Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
View File
+21
View File
@@ -0,0 +1,21 @@
objc_library(
name = "DarwinDirStat",
enable_modules = True,
module_name = "DarwinDirStat",
srcs = glob([
"Sources/*.m",
]),
hdrs = glob([
"PublicHeaders/**/*.h",
]),
includes = [
"PublicHeaders",
],
sdk_frameworks = [
"Foundation",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,32 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "DarwinDirStat",
platforms: [.macOS(.v10_13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "DarwinDirStat",
targets: ["DarwinDirStat"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "DarwinDirStat",
dependencies: [],
path: ".",
exclude: ["BUILD"],
publicHeadersPath: "PublicHeaders",
cSettings: [
.headerSearchPath("PublicHeaders")
]),
]
)
@@ -0,0 +1,14 @@
#ifndef DarwinDirStat_h
#define DarwinDirStat_h
#import <Foundation/Foundation.h>
struct darwin_dirstat {
off_t total_size;
uint64_t descendants;
};
int dirstat_np(const char *path, int flags, struct darwin_dirstat *ds, size_t dirstat_size);
#endif
@@ -0,0 +1,2 @@
#import <DarwinDirStat/DarwinDirStat.h>
+20
View File
@@ -0,0 +1,20 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "DeviceModel",
module_name = "DeviceModel",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/LegacyComponents",
"//submodules/AccountContext",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,430 @@
import Foundation
public enum DeviceModel: CaseIterable, Equatable {
public static var allCases: [DeviceModel] {
return [
.iPodTouch1,
.iPodTouch2,
.iPodTouch3,
.iPodTouch4,
.iPodTouch5,
.iPodTouch6,
.iPodTouch7,
.iPhone,
.iPhone3G,
.iPhone3GS,
.iPhone4,
.iPhone4S,
.iPhone5,
.iPhone5C,
.iPhone5S,
.iPhone6,
.iPhone6Plus,
.iPhone6S,
.iPhone6SPlus,
.iPhoneSE,
.iPhone7,
.iPhone7Plus,
.iPhone8,
.iPhone8Plus,
.iPhoneX,
.iPhoneXS,
.iPhoneXR,
.iPhone11,
.iPhone11Pro,
.iPhone11ProMax,
.iPhone12,
.iPhone12Mini,
.iPhone12Pro,
.iPhone12ProMax,
.iPhone13,
.iPhone13Mini,
.iPhone13Pro,
.iPhone13ProMax,
.iPhone14,
.iPhone14Plus,
.iPhone14Pro,
.iPhone14ProMax,
.iPhone15,
.iPhone15Plus,
.iPhone15Pro,
.iPhone15ProMax,
.iPhone16,
.iPhone16Plus,
.iPhone16Pro,
.iPhone16ProMax
]
}
case iPodTouch1
case iPodTouch2
case iPodTouch3
case iPodTouch4
case iPodTouch5
case iPodTouch6
case iPodTouch7
case iPhone
case iPhone3G
case iPhone3GS
case iPhone4
case iPhone4S
case iPhone5
case iPhone5C
case iPhone5S
case iPhone6
case iPhone6Plus
case iPhone6S
case iPhone6SPlus
case iPhoneSE
case iPhone7
case iPhone7Plus
case iPhone8
case iPhone8Plus
case iPhoneX
case iPhoneXS
case iPhoneXSMax
case iPhoneXR
case iPhone11
case iPhone11Pro
case iPhone11ProMax
case iPhoneSE2ndGen
case iPhone12
case iPhone12Mini
case iPhone12Pro
case iPhone12ProMax
case iPhone13
case iPhone13Mini
case iPhone13Pro
case iPhone13ProMax
case iPhoneSE3rdGen
case iPhone14
case iPhone14Plus
case iPhone14Pro
case iPhone14ProMax
case iPhone15
case iPhone15Plus
case iPhone15Pro
case iPhone15ProMax
case iPhone16
case iPhone16Plus
case iPhone16Pro
case iPhone16ProMax
case iPhone16e
case iPhone17
case iPhone17Pro
case iPhone17ProMax
case iPhoneAir
case unknown(String)
public var modelId: [String] {
switch self {
case .iPodTouch1:
return ["iPod1,1"]
case .iPodTouch2:
return ["iPod2,1"]
case .iPodTouch3:
return ["iPod3,1"]
case .iPodTouch4:
return ["iPod4,1"]
case .iPodTouch5:
return ["iPod5,1"]
case .iPodTouch6:
return ["iPod7,1"]
case .iPodTouch7:
return ["iPod9,1"]
case .iPhone:
return ["iPhone1,1"]
case .iPhone3G:
return ["iPhone1,2"]
case .iPhone3GS:
return ["iPhone2,1"]
case .iPhone4:
return ["iPhone3,1", "iPhone3,2", "iPhone3,3"]
case .iPhone4S:
return ["iPhone4,1", "iPhone4,2", "iPhone4,3"]
case .iPhone5:
return ["iPhone5,1", "iPhone5,2"]
case .iPhone5C:
return ["iPhone5,3", "iPhone5,4"]
case .iPhone5S:
return ["iPhone6,1", "iPhone6,2"]
case .iPhone6:
return ["iPhone7,2"]
case .iPhone6Plus:
return ["iPhone7,1"]
case .iPhone6S:
return ["iPhone8,1"]
case .iPhone6SPlus:
return ["iPhone8,2"]
case .iPhoneSE:
return ["iPhone8,4"]
case .iPhone7:
return ["iPhone9,1", "iPhone9,3"]
case .iPhone7Plus:
return ["iPhone9,2", "iPhone9,4"]
case .iPhone8:
return ["iPhone10,1", "iPhone10,4"]
case .iPhone8Plus:
return ["iPhone10,2", "iPhone10,5"]
case .iPhoneX:
return ["iPhone10,3", "iPhone10,6"]
case .iPhoneXS:
return ["iPhone11,2"]
case .iPhoneXSMax:
return ["iPhone11,4", "iPhone11,6"]
case .iPhoneXR:
return ["iPhone11,8"]
case .iPhone11:
return ["iPhone12,1"]
case .iPhone11Pro:
return ["iPhone12,3"]
case .iPhone11ProMax:
return ["iPhone12,5"]
case .iPhoneSE2ndGen:
return ["iPhone12,8"]
case .iPhone12:
return ["iPhone13,2"]
case .iPhone12Mini:
return ["iPhone13,1"]
case .iPhone12Pro:
return ["iPhone13,3"]
case .iPhone12ProMax:
return ["iPhone13,4"]
case .iPhone13:
return ["iPhone14,5"]
case .iPhone13Mini:
return ["iPhone14,4"]
case .iPhone13Pro:
return ["iPhone14,2"]
case .iPhone13ProMax:
return ["iPhone14,3"]
case .iPhoneSE3rdGen:
return ["iPhone14,6"]
case .iPhone14:
return ["iPhone14,7"]
case .iPhone14Plus:
return ["iPhone14,8"]
case .iPhone14Pro:
return ["iPhone15,2"]
case .iPhone14ProMax:
return ["iPhone15,3"]
case .iPhone15:
return ["iPhone15,4"]
case .iPhone15Plus:
return ["iPhone15,5"]
case .iPhone15Pro:
return ["iPhone16,1"]
case .iPhone15ProMax:
return ["iPhone16,2"]
case .iPhone16:
return ["iPhone17,3"]
case .iPhone16Plus:
return ["iPhone17,4"]
case .iPhone16Pro:
return ["iPhone17,1"]
case .iPhone16ProMax:
return ["iPhone17,2"]
case .iPhone16e:
return ["iPhone17,5"]
case .iPhone17:
return ["iPhone18,3"]
case .iPhone17Pro:
return ["iPhone18,1"]
case .iPhone17ProMax:
return ["iPhone18,2"]
case .iPhoneAir:
return ["iPhone18,4"]
case let .unknown(modelId):
return [modelId]
}
}
public var modelName: String {
switch self {
case .iPodTouch1:
return "iPod touch 1G"
case .iPodTouch2:
return "iPod touch 2G"
case .iPodTouch3:
return "iPod touch 3G"
case .iPodTouch4:
return "iPod touch 4G"
case .iPodTouch5:
return "iPod touch 5G"
case .iPodTouch6:
return "iPod touch 6G"
case .iPodTouch7:
return "iPod touch 7G"
case .iPhone:
return "iPhone"
case .iPhone3G:
return "iPhone 3G"
case .iPhone3GS:
return "iPhone 3GS"
case .iPhone4:
return "iPhone 4"
case .iPhone4S:
return "iPhone 4S"
case .iPhone5:
return "iPhone 5"
case .iPhone5C:
return "iPhone 5C"
case .iPhone5S:
return "iPhone 5S"
case .iPhone6:
return "iPhone 6"
case .iPhone6Plus:
return "iPhone 6 Plus"
case .iPhone6S:
return "iPhone 6S"
case .iPhone6SPlus:
return "iPhone 6S Plus"
case .iPhoneSE:
return "iPhone SE"
case .iPhone7:
return "iPhone 7"
case .iPhone7Plus:
return "iPhone 7 Plus"
case .iPhone8:
return "iPhone 8"
case .iPhone8Plus:
return "iPhone 8 Plus"
case .iPhoneX:
return "iPhone X"
case .iPhoneXS:
return "iPhone XS"
case .iPhoneXSMax:
return "iPhone XS Max"
case .iPhoneXR:
return "iPhone XR"
case .iPhone11:
return "iPhone 11"
case .iPhone11Pro:
return "iPhone 11 Pro"
case .iPhone11ProMax:
return "iPhone 11 Pro Max"
case .iPhoneSE2ndGen:
return "iPhone SE (2nd gen)"
case .iPhone12:
return "iPhone 12"
case .iPhone12Mini:
return "iPhone 12 mini"
case .iPhone12Pro:
return "iPhone 12 Pro"
case .iPhone12ProMax:
return "iPhone 12 Pro Max"
case .iPhone13:
return "iPhone 13"
case .iPhone13Mini:
return "iPhone 13 mini"
case .iPhone13Pro:
return "iPhone 13 Pro"
case .iPhone13ProMax:
return "iPhone 13 Pro Max"
case .iPhoneSE3rdGen:
return "iPhone SE (3rd gen)"
case .iPhone14:
return "iPhone 14"
case .iPhone14Plus:
return "iPhone 14 Plus"
case .iPhone14Pro:
return "iPhone 14 Pro"
case .iPhone14ProMax:
return "iPhone 14 Pro Max"
case .iPhone15:
return "iPhone 15"
case .iPhone15Plus:
return "iPhone 15 Plus"
case .iPhone15Pro:
return "iPhone 15 Pro"
case .iPhone15ProMax:
return "iPhone 15 Pro Max"
case .iPhone16:
return "iPhone 16"
case .iPhone16Plus:
return "iPhone 16 Plus"
case .iPhone16Pro:
return "iPhone 16 Pro"
case .iPhone16ProMax:
return "iPhone 16 Pro Max"
case .iPhone16e:
return "iPhone 16e"
case .iPhone17:
return "iPhone 17"
case .iPhone17Pro:
return "iPhone 17 Pro"
case .iPhone17ProMax:
return "iPhone 17 Pro Max"
case .iPhoneAir:
return "iPhone Air"
case let .unknown(modelId):
if modelId.hasPrefix("iPhone") {
return "Unknown iPhone"
} else if modelId.hasPrefix("iPod") {
return "Unknown iPod"
} else if modelId.hasPrefix("iPad") {
return "Unknown iPad"
} else {
return "Unknown Device"
}
}
}
public var isIpad: Bool {
return self.modelId.first?.hasPrefix("iPad") ?? false
}
public static let current = DeviceModel()
public static func currentModelCode() -> String {
var systemInfo = utsname()
uname(&systemInfo)
let modelCode = withUnsafePointer(to: &systemInfo.machine) {
$0.withMemoryRebound(to: CChar.self, capacity: 1) {
ptr in String.init(validatingUTF8: ptr)
}
}
return modelCode ?? "unknown"
}
private init() {
var systemInfo = utsname()
uname(&systemInfo)
let modelCode = withUnsafePointer(to: &systemInfo.machine) {
$0.withMemoryRebound(to: CChar.self, capacity: 1) {
ptr in String.init(validatingUTF8: ptr)
}
}
var result: DeviceModel?
if let modelCode {
for model in DeviceModel.allCases {
if model.modelId.contains(modelCode) {
result = model
break
}
}
}
if let result {
self = result
} else {
self = .unknown(modelCode ?? "")
}
}
}
+24
View File
@@ -0,0 +1,24 @@
objc_library(
name = "LokiRng",
enable_modules = True,
module_name = "LokiRng",
srcs = glob([
"Sources/**/*.m",
"Sources/**/*.mm",
"Sources/**/*.h",
"Sources/**/*.cpp",
], allow_empty=True),
hdrs = glob([
"PublicHeaders/**/*.h",
]),
includes = [
"PublicHeaders",
],
sdk_frameworks = [
"Foundation",
],
visibility = [
"//visibility:public",
],
)
+28
View File
@@ -0,0 +1,28 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "LokiRng",
platforms: [.macOS(.v10_13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "LokiRng",
targets: ["LokiRng"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "LokiRng",
dependencies: [],
path: ".",
publicHeadersPath: "PublicHeaders"),
]
)
@@ -0,0 +1,15 @@
#ifndef LokiRng_h
#define LokiRng_h
#import <Foundation/Foundation.h>
@interface LokiRng : NSObject
- (instancetype _Nonnull)initWithSeed0:(NSUInteger)seed0 seed1:(NSUInteger)seed1 seed2:(NSUInteger)seed2;
- (float)next;
+ (float)randomWithSeed0:(NSUInteger)seed0 seed1:(NSUInteger)seed1 seed2:(NSUInteger)seed2;
@end
#endif /* LokiRng_h */
@@ -0,0 +1,94 @@
#import <LokiRng/LokiRng.h>
static uint32_t tausStep(const uint32_t z, const int32_t s1, const int32_t s2, const int32_t s3, const uint32_t M) {
uint32_t b = (((z << s1) ^ z) >> s2);
return (((z & M) << s3) ^ b);
}
@interface LokiRng () {
float _seed;
}
@end
@implementation LokiRng
- (instancetype _Nonnull)initWithSeed0:(NSUInteger)seed0 seed1:(NSUInteger)seed1 seed2:(NSUInteger)seed2 {
self = [super init];
if (self != nil) {
uint32_t seed = ((uint32_t)seed0) * 1099087573U;
uint32_t seedb = ((uint32_t)seed1) * 1099087573U;
uint32_t seedc = ((uint32_t)seed2) * 1099087573U;
// Round 1: Randomise seed
uint32_t z1 = tausStep(seed,13,19,12,429496729U);
uint32_t z2 = tausStep(seed,2,25,4,4294967288U);
uint32_t z3 = tausStep(seed,3,11,17,429496280U);
uint32_t z4 = (1664525*seed + 1013904223U);
// Round 2: Randomise seed again using second seed
uint32_t r1 = (z1^z2^z3^z4^seedb);
z1 = tausStep(r1,13,19,12,429496729U);
z2 = tausStep(r1,2,25,4,4294967288U);
z3 = tausStep(r1,3,11,17,429496280U);
z4 = (1664525*r1 + 1013904223U);
// Round 3: Randomise seed again using third seed
r1 = (z1^z2^z3^z4^seedc);
z1 = tausStep(r1,13,19,12,429496729U);
z2 = tausStep(r1,2,25,4,4294967288U);
z3 = tausStep(r1,3,11,17,429496280U);
z4 = (1664525*r1 + 1013904223U);
_seed = (z1^z2^z3^z4) * 2.3283064365387e-10f;
}
return self;
}
- (float)next {
uint32_t hashed_seed = _seed * 1099087573U;
uint32_t z1 = tausStep(hashed_seed,13,19,12,429496729U);
uint32_t z2 = tausStep(hashed_seed,2,25,4,4294967288U);
uint32_t z3 = tausStep(hashed_seed,3,11,17,429496280U);
uint32_t z4 = (1664525*hashed_seed + 1013904223U);
float old_seed = _seed;
_seed = (z1^z2^z3^z4) * 2.3283064365387e-10f;
return old_seed;
}
+ (float)randomWithSeed0:(NSUInteger)seed0 seed1:(NSUInteger)seed1 seed2:(NSUInteger)seed2 {
uint32_t seed = ((uint32_t)seed0) * 1099087573U;
uint32_t seedb = ((uint32_t)seed1) * 1099087573U;
uint32_t seedc = ((uint32_t)seed2) * 1099087573U;
// Round 1: Randomise seed
uint32_t z1 = tausStep(seed,13,19,12,429496729U);
uint32_t z2 = tausStep(seed,2,25,4,4294967288U);
uint32_t z3 = tausStep(seed,3,11,17,429496280U);
uint32_t z4 = (1664525*seed + 1013904223U);
// Round 2: Randomise seed again using second seed
uint32_t r1 = (z1^z2^z3^z4^seedb);
z1 = tausStep(r1,13,19,12,429496729U);
z2 = tausStep(r1,2,25,4,4294967288U);
z3 = tausStep(r1,3,11,17,429496280U);
z4 = (1664525*r1 + 1013904223U);
// Round 3: Randomise seed again using third seed
r1 = (z1^z2^z3^z4^seedc);
z1 = tausStep(r1,13,19,12,429496729U);
z2 = tausStep(r1,2,25,4,4294967288U);
z3 = tausStep(r1,3,11,17,429496280U);
z4 = (1664525*r1 + 1013904223U);
return (z1^z2^z3^z4) * 2.3283064365387e-10f;
}
@end
+15
View File
@@ -0,0 +1,15 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "RangeSet",
module_name = "RangeSet",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
visibility = [
"//visibility:public",
],
)
+27
View File
@@ -0,0 +1,27 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "RangeSet",
platforms: [.macOS(.v10_13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "RangeSet",
targets: ["RangeSet"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "RangeSet",
dependencies: [],
path: "Sources")
]
)
@@ -0,0 +1,269 @@
//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
// MARK: Subscripts
extension Collection {
/// Accesses a view of this collection with the elements at the given
/// indices.
///
/// - Parameter subranges: The indices of the elements to retrieve from this
/// collection.
/// - Returns: A collection of the elements at the positions in `subranges`.
///
/// - Complexity: O(1)
public subscript(subranges: RangeSet<Index>) -> DiscontiguousSlice<Self> {
DiscontiguousSlice(base: self, subranges: subranges)
}
}
extension MutableCollection {
/// Accesses a mutable view of this collection with the elements at the
/// given indices.
///
/// - Parameter subranges: The ranges of the elements to retrieve from this
/// collection.
/// - Returns: A collection of the elements at the positions in `subranges`.
///
/// - Complexity: O(1) to access the elements, O(*m*) to mutate the
/// elements at the positions in `subranges`, where *m* is the number of
/// elements indicated by `subranges`.
public subscript(subranges: RangeSet<Index>) -> DiscontiguousSlice<Self> {
get {
DiscontiguousSlice(base: self, subranges: subranges)
}
set {
for i in newValue.indices where subranges.contains(i.base) {
self[i.base] = newValue[i]
}
}
}
}
// MARK: - moveSubranges(_:to:)
extension MutableCollection {
/// Moves the elements in the given subranges to just before the element at
/// the specified index.
///
/// This example finds all the uppercase letters in the array and then
/// moves them to between `"i"` and `"j"`.
///
/// var letters = Array("ABCdeFGhijkLMNOp")
/// let uppercaseRanges = letters.subranges(where: { $0.isUppercase })
/// let rangeOfUppercase = letters.moveSubranges(uppercaseRanges, to: 10)
/// // String(letters) == "dehiABCFGLMNOjkp"
/// // rangeOfUppercase == 4..<13
///
/// - Parameters:
/// - subranges: The subranges of the elements to move.
/// - insertionPoint: The index to use as the destination of the elements.
/// - Returns: The new bounds of the moved elements.
///
/// - Complexity: O(*n* log *n*) where *n* is the length of the collection.
@discardableResult
public mutating func moveSubranges(
_ subranges: RangeSet<Index>, to insertionPoint: Index
) -> Range<Index> {
let lowerCount = distance(from: startIndex, to: insertionPoint)
let upperCount = distance(from: insertionPoint, to: endIndex)
let start = _indexedStablePartition(
count: lowerCount,
range: startIndex..<insertionPoint,
by: { subranges.contains($0) })
let end = _indexedStablePartition(
count: upperCount,
range: insertionPoint..<endIndex,
by: { !subranges.contains($0) })
return start..<end
}
}
// MARK: - removeSubranges(_:) / removingSubranges(_:)
extension RangeReplaceableCollection {
/// Removes the elements at the given indices.
///
/// For example, this code sample finds the indices of all the vowel
/// characters in the string, and then removes those characters.
///
/// var str = "The rain in Spain stays mainly in the plain."
/// let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
/// let vowelIndices = str.subranges(where: { vowels.contains($0) })
///
/// str.removeSubranges(vowelIndices)
/// // str == "Th rn n Spn stys mnly n th pln."
///
/// - Parameter subranges: The indices of the elements to remove.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
public mutating func removeSubranges(_ subranges: RangeSet<Index>) {
guard !subranges.isEmpty else {
return
}
let inversion = subranges._inverted(within: self)
var result = Self()
for range in inversion.ranges {
result.append(contentsOf: self[range])
}
self = result
}
}
extension MutableCollection where Self: RangeReplaceableCollection {
/// Removes the elements at the given indices.
///
/// For example, this code sample finds the indices of all the negative
/// numbers in the array, and then removes those values.
///
/// var numbers = [5, 7, -3, -8, 11, 2, -1, 6]
/// let negativeIndices = numbers.subranges(where: { $0 < 0 })
///
/// numbers.removeSubranges(negativeIndices)
/// // numbers == [5, 7, 11, 2, 6]
///
/// - Parameter subranges: The indices of the elements to remove.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
public mutating func removeSubranges(_ subranges: RangeSet<Index>) {
guard let firstRange = subranges.ranges.first else {
return
}
var endOfElementsToKeep = firstRange.lowerBound
var firstUnprocessed = firstRange.upperBound
// This performs a half-stable partition based on the ranges in
// `indices`. At all times, the collection is divided into three
// regions:
//
// - `self[..<endOfElementsToKeep]` contains only elements that will
// remain in the collection after this method call.
// - `self[endOfElementsToKeep..<firstUnprocessed]` contains only
// elements that will be removed.
// - `self[firstUnprocessed...]` contains a mix of elements to remain
// and elements to be removed.
//
// Each iteration of this loop moves the elements that are _between_
// two ranges to remove from the third region to the first region.
for range in subranges.ranges.dropFirst() {
let nextLow = range.lowerBound
while firstUnprocessed != nextLow {
swapAt(endOfElementsToKeep, firstUnprocessed)
formIndex(after: &endOfElementsToKeep)
formIndex(after: &firstUnprocessed)
}
firstUnprocessed = range.upperBound
}
// After dealing with all the ranges in `indices`, move the elements
// that are still in the third region down to the first.
while firstUnprocessed != endIndex {
swapAt(endOfElementsToKeep, firstUnprocessed)
formIndex(after: &endOfElementsToKeep)
formIndex(after: &firstUnprocessed)
}
removeSubrange(endOfElementsToKeep..<endIndex)
}
}
extension Collection {
/// Returns a collection of the elements in this collection that are not
/// represented by the given range set.
///
/// For example, this code sample finds the indices of all the vowel
/// characters in the string, and then retrieves a collection that omits
/// those characters.
///
/// let str = "The rain in Spain stays mainly in the plain."
/// let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
/// let vowelIndices = str.subranges(where: { vowels.contains($0) })
///
/// let disemvoweled = str.removingSubranges(vowelIndices)
/// print(String(disemvoweled))
/// // Prints "Th rn n Spn stys mnly n th pln."
///
/// - Parameter subranges: A range set representing the indices of the
/// elements to remove.
/// - Returns: A collection of the elements that are not in `subranges`.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
public func removingSubranges(
_ subranges: RangeSet<Index>
) -> DiscontiguousSlice<Self> {
let inversion = subranges._inverted(within: self)
return self[inversion]
}
}
// MARK: - subranges(where:) / subranges(of:)
extension Collection {
/// Returns the indices of all the elements that match the given predicate.
///
/// For example, you can use this method to find all the places that a
/// vowel occurs in a string.
///
/// let str = "Fresh cheese in a breeze"
/// let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
/// let allTheVowels = str.subranges(where: { vowels.contains($0) })
/// // str[allTheVowels].count == 9
///
/// - Parameter predicate: A closure that takes an element as its argument
/// and returns a Boolean value that indicates whether the passed element
/// represents a match.
/// - Returns: A set of the indices of the elements for which `predicate`
/// returns `true`.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
public func subranges(where predicate: (Element) throws -> Bool) rethrows
-> RangeSet<Index>
{
if isEmpty { return RangeSet() }
var result = RangeSet<Index>()
var i = startIndex
while i != endIndex {
let next = index(after: i)
if try predicate(self[i]) {
result._append(i..<next)
}
i = next
}
return result
}
}
extension Collection where Element: Equatable {
/// Returns the indices of all the elements that are equal to the given
/// element.
///
/// For example, you can use this method to find all the places that a
/// particular letter occurs in a string.
///
/// let str = "Fresh cheese in a breeze"
/// let allTheEs = str.subranges(of: "e")
/// // str[allTheEs].count == 7
///
/// - Parameter element: An element to look for in the collection.
/// - Returns: A set of the indices of the elements that are equal to
/// `element`.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
public func subranges(of element: Element) -> RangeSet<Index> {
subranges(where: { $0 == element })
}
}
@@ -0,0 +1,140 @@
//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
/// A collection wrapper that provides access to the elements of a collection,
/// indexed by a set of indices.
public struct DiscontiguousSlice<Base: Collection> {
/// The collection that the indexed collection wraps.
public var base: Base
/// The set of subranges that are available through this discontiguous slice.
public var subranges: RangeSet<Base.Index>
}
extension DiscontiguousSlice {
/// A position in an `DiscontiguousSlice`.
public struct Index: Comparable {
/// The index of the range that contains `base`.
internal var _rangeOffset: Int
/// The position of this index in the base collection.
public var base: Base.Index
public static func < (lhs: Index, rhs: Index) -> Bool {
lhs.base < rhs.base
}
}
}
extension DiscontiguousSlice.Index: Hashable where Base.Index: Hashable {}
extension DiscontiguousSlice: Collection {
public typealias SubSequence = Self
public var startIndex: Index {
subranges.isEmpty
? endIndex
: Index(_rangeOffset: 0, base: subranges._ranges[0].lowerBound)
}
public var endIndex: Index {
Index(_rangeOffset: subranges._ranges.endIndex, base: base.endIndex)
}
public func index(after i: Index) -> Index {
let nextIndex = base.index(after: i.base)
if subranges._ranges[i._rangeOffset].contains(nextIndex) {
return Index(_rangeOffset: i._rangeOffset, base: nextIndex)
}
let nextOffset = i._rangeOffset + 1
if nextOffset < subranges._ranges.endIndex {
return Index(
_rangeOffset: nextOffset,
base: subranges._ranges[nextOffset].lowerBound)
} else {
return endIndex
}
}
public subscript(i: Index) -> Base.Element {
base[i.base]
}
public subscript(bounds: Range<Index>) -> DiscontiguousSlice<Base> {
let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base
let subset = subranges.intersection(RangeSet(baseBounds))
return DiscontiguousSlice<Base>(base: base, subranges: subset)
}
}
extension DiscontiguousSlice {
public var count: Int {
var c = 0
for range in subranges._ranges {
c += base.distance(from: range.lowerBound, to: range.upperBound)
}
return c
}
public __consuming func _copyToContiguousArray() -> ContiguousArray<Base.Element> {
var result: ContiguousArray<Base.Element> = []
for range in subranges._ranges {
result.append(contentsOf: base[range])
}
return result
}
}
extension DiscontiguousSlice: BidirectionalCollection
where Base: BidirectionalCollection
{
public func index(before i: Index) -> Index {
precondition(i != startIndex, "Can't move index before startIndex")
if i == endIndex || i.base == subranges._ranges[i._rangeOffset].lowerBound {
let offset = i._rangeOffset - 1
return Index(
_rangeOffset: offset,
base: base.index(before: subranges._ranges[offset].upperBound))
}
return Index(
_rangeOffset: i._rangeOffset,
base: base.index(before: i.base))
}
}
extension DiscontiguousSlice: MutableCollection where Base: MutableCollection {
public subscript(i: Index) -> Base.Element {
get {
base[i.base]
}
set {
base[i.base] = newValue
}
}
public subscript(bounds: Range<Index>) -> DiscontiguousSlice<Base> {
get {
let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base
let subset = subranges.intersection(RangeSet(baseBounds))
return DiscontiguousSlice<Base>(base: base, subranges: subset)
}
set {
let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base
let subset = subranges.intersection(RangeSet(baseBounds))
for i in newValue.indices where subset.contains(i.base) {
base[i.base] = newValue[i]
}
}
}
}
@@ -0,0 +1,33 @@
//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
/// A collection of two elements, to avoid heap allocation when calling
/// `replaceSubrange` with just two elements.
internal struct Pair<Element>: RandomAccessCollection {
var pair: (first: Element, second: Element)
init(_ first: Element, _ second: Element) {
self.pair = (first, second)
}
var startIndex: Int { 0 }
var endIndex: Int { 2 }
subscript(position: Int) -> Element {
get {
switch position {
case 0: return pair.first
case 1: return pair.second
default: fatalError("Index '\(position)' is out of range")
}
}
}
}
@@ -0,0 +1,217 @@
//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
// MARK: _rotate(in:shiftingToStart:)
extension MutableCollection {
/// Rotates the elements of the collection so that the element at `middle`
/// ends up first.
///
/// - Returns: The new index of the element that was first pre-rotation.
///
/// - Complexity: O(*n*)
@discardableResult
internal mutating func _rotate(in subrange: Range<Index>, shiftingToStart middle: Index) -> Index {
var m = middle, s = subrange.lowerBound
let e = subrange.upperBound
// Handle the trivial cases
if s == m { return e }
if m == e { return s }
// We have two regions of possibly-unequal length that need to be
// exchanged. The return value of this method is going to be the
// position following that of the element that is currently last
// (element j).
//
// [a b c d e f g|h i j] or [a b c|d e f g h i j]
// ^ ^ ^ ^ ^ ^
// s m e s m e
//
var ret = e // start with a known incorrect result.
while true {
// Exchange the leading elements of each region (up to the
// length of the shorter region).
//
// [a b c d e f g|h i j] or [a b c|d e f g h i j]
// ^^^^^ ^^^^^ ^^^^^ ^^^^^
// [h i j d e f g|a b c] or [d e f|a b c g h i j]
// ^ ^ ^ ^ ^ ^ ^ ^
// s s1 m m1/e s s1/m m1 e
//
let (s1, m1) = _swapNonemptySubrangePrefixes(s..<m, m..<e)
if m1 == e {
// Left-hand case: we have moved element j into position. if
// we haven't already, we can capture the return value which
// is in s1.
//
// Note: the STL breaks the loop into two just to avoid this
// comparison once the return value is known. I'm not sure
// it's a worthwhile optimization, though.
if ret == e { ret = s1 }
// If both regions were the same size, we're done.
if s1 == m { break }
}
// Now we have a smaller problem that is also a rotation, so we
// can adjust our bounds and repeat.
//
// h i j[d e f g|a b c] or d e f[a b c|g h i j]
// ^ ^ ^ ^ ^ ^
// s m e s m e
s = s1
if s == m { m = m1 }
}
return ret
}
/// Swaps the elements of the two given subranges, up to the upper bound of
/// the smaller subrange. The returned indices are the ends of the two
/// ranges that were actually swapped.
///
/// Input:
/// [a b c d e f g h i j k l m n o p]
/// ^^^^^^^ ^^^^^^^^^^^^^
/// lhs rhs
///
/// Output:
/// [i j k l e f g h a b c d m n o p]
/// ^ ^
/// p q
///
/// - Precondition: !lhs.isEmpty && !rhs.isEmpty
/// - Postcondition: For returned indices `(p, q)`:
///
/// - distance(from: lhs.lowerBound, to: p) == distance(from:
/// rhs.lowerBound, to: q)
/// - p == lhs.upperBound || q == rhs.upperBound
mutating func _swapNonemptySubrangePrefixes(
_ lhs: Range<Index>, _ rhs: Range<Index>
) -> (Index, Index) {
assert(!lhs.isEmpty)
assert(!rhs.isEmpty)
var p = lhs.lowerBound
var q = rhs.lowerBound
repeat {
swapAt(p, q)
formIndex(after: &p)
formIndex(after: &q)
}
while p != lhs.upperBound && q != rhs.upperBound
return (p, q)
}
}
// MARK: - _stablePartition(count:range:by:)
extension MutableCollection {
/// Moves all elements satisfying `belongsInSecondPartition` into a suffix
/// of the collection, preserving their relative order, and returns the
/// start of the resulting suffix.
///
/// - Complexity: O(*n* log *n*) where *n* is the number of elements.
/// - Precondition:
/// `n == distance(from: range.lowerBound, to: range.upperBound)`
internal mutating func _stablePartition(
count n: Int,
range: Range<Index>,
by belongsInSecondPartition: (Element) throws-> Bool
) rethrows -> Index {
if n == 0 { return range.lowerBound }
if n == 1 {
return try belongsInSecondPartition(self[range.lowerBound])
? range.lowerBound
: range.upperBound
}
let h = n / 2, i = index(range.lowerBound, offsetBy: h)
let j = try _stablePartition(
count: h,
range: range.lowerBound..<i,
by: belongsInSecondPartition)
let k = try _stablePartition(
count: n - h,
range: i..<range.upperBound,
by: belongsInSecondPartition)
return _rotate(in: j..<k, shiftingToStart: i)
}
/// Moves all elements at the indices satisfying `belongsInSecondPartition`
/// into a suffix of the collection, preserving their relative order, and
/// returns the start of the resulting suffix.
///
/// - Complexity: O(*n* log *n*) where *n* is the number of elements.
/// - Precondition:
/// `n == distance(from: range.lowerBound, to: range.upperBound)`
internal mutating func _indexedStablePartition(
count n: Int,
range: Range<Index>,
by belongsInSecondPartition: (Index) throws-> Bool
) rethrows -> Index {
if n == 0 { return range.lowerBound }
if n == 1 {
return try belongsInSecondPartition(range.lowerBound)
? range.lowerBound
: range.upperBound
}
let h = n / 2, i = index(range.lowerBound, offsetBy: h)
let j = try _indexedStablePartition(
count: h,
range: range.lowerBound..<i,
by: belongsInSecondPartition)
let k = try _indexedStablePartition(
count: n - h,
range: i..<range.upperBound,
by: belongsInSecondPartition)
return _rotate(in: j..<k, shiftingToStart: i)
}
}
// MARK: - partitioningIndex(where:)
extension Collection {
/// Returns the index of the first element in the collection that matches
/// the predicate.
///
/// The collection must already be partitioned according to the predicate.
/// That is, there should be an index `i` where for every element in
/// `collection[..<i]` the predicate is `false`, and for every element
/// in `collection[i...]` the predicate is `true`.
///
/// - Parameter predicate: A predicate that partitions the collection.
/// - Returns: The index of the first element in the collection for which
/// `predicate` returns `true`.
///
/// - Complexity: O(log *n*), where *n* is the length of this collection if
/// the collection conforms to `RandomAccessCollection`, otherwise O(*n*).
internal func _partitioningIndex(
where predicate: (Element) throws -> Bool
) rethrows -> Index {
var n = count
var l = startIndex
while n > 0 {
let half = n / 2
let mid = index(l, offsetBy: half)
if try predicate(self[mid]) {
n = half
} else {
l = index(after: mid)
n -= half + 1
}
}
return l
}
}
@@ -0,0 +1,564 @@
//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
/// A set of values of any comparable type, represented by ranges.
///
/// You can use a range set to efficiently represent a set of `Comparable`
/// values that spans any number of discontiguous ranges. Range sets are
/// commonly used to represent multiple subranges of a collection, by storing
/// ranges of a collection's index type.
///
/// In this example, `negativeSubranges` is a range set representing the
/// locations of all the negative values in `numbers`:
///
/// var numbers = [10, 12, -5, 14, -3, -9, 15]
/// let negativeSubranges = numbers.subranges(where: { $0 < 0 })
/// // numbers[negativeSubranges].count == 3
///
/// numbers.moveSubranges(negativeSubranges, to: 0)
/// // numbers == [-5, -3, -9, 10, 12, 14, 15]
public struct RangeSet<Bound: Comparable> {
internal var _ranges = _RangeSetStorage<Bound>()
/// Creates an empty range set.
public init() {}
/// Creates a range set containing the given range.
///
/// - Parameter range: The range to use for the new range set.
public init(_ range: Range<Bound>) {
if !range.isEmpty {
self._ranges = _RangeSetStorage(range)
}
}
/// Creates a range set containing the values in the given ranges.
///
/// Any empty ranges in `ranges` are ignored, and non-empty ranges are merged
/// to eliminate any overlaps. As such, the `ranges` collection in the
/// resulting range set may not be equivalent to the sequence of ranges
/// passed to this initializer.
///
/// - Parameter ranges: The ranges to use for the new range set.
public init<S: Sequence>(_ ranges: S) where S.Element == Range<Bound> {
for range in ranges {
insert(contentsOf: range)
}
}
/// Checks the invariants of `_ranges`.
///
/// The ranges stored by a range set are never empty, never overlap,
/// and are always stored in ascending order when comparing their lower
/// or upper bounds. In addition to not overlapping, no two consecutive
/// ranges share an upper and lower bound `[0..<5, 5..<10]` is ill-formed,
/// and would instead be represented as `[0..<10]`.
internal func _checkInvariants() {
for (a, b) in zip(ranges, ranges.dropFirst()) {
precondition(!a.isEmpty && !b.isEmpty, "Empty range in range set")
precondition(
a.upperBound < b.lowerBound,
"Out of order/overlapping ranges in range set")
}
}
/// Creates a new range set from `ranges`, which satisfies the range set
/// invariants.
internal init(_orderedRanges ranges: [Range<Bound>]) {
self._ranges = _RangeSetStorage(ranges)
_checkInvariants()
}
/// A Boolean value indicating whether the range set is empty.
public var isEmpty: Bool {
_ranges.isEmpty
}
/// Returns a Boolean value indicating whether the given value is
/// contained by the ranges in the range set.
///
/// - Parameter value: The value to look for in the range set.
/// - Returns: `true` if `value` is contained by a range in the range set;
/// otherwise, `false`.
///
/// - Complexity: O(log *n*), where *n* is the number of ranges in the
/// range set.
public func contains(_ value: Bound) -> Bool {
let i = _ranges._partitioningIndex { $0.upperBound > value }
return i == _ranges.endIndex
? false
: _ranges[i].lowerBound <= value
}
public func intersects(_ range: Range<Bound>) -> Bool {
if _ranges.isEmpty {
return false
}
if range.isEmpty {
return false
}
if range.lowerBound > _ranges.last!.upperBound {
return false
}
if range.upperBound < _ranges.first!.lowerBound {
return false
}
return !_indicesOfRange(range).isEmpty
}
/// Returns a range indicating the existing ranges that `range` overlaps
/// with.
///
/// For example, if `self` is `[0..<5, 10..<15, 20..<25, 30..<35]`, then:
///
/// - `_indicesOfRange(12..<14) == 1..<2`
/// - `_indicesOfRange(12..<19) == 1..<2`
/// - `_indicesOfRange(17..<19) == 2..<2`
/// - `_indicesOfRange(12..<22) == 1..<3`
func _indicesOfRange(_ range: Range<Bound>) -> Range<Int> {
precondition(!range.isEmpty)
precondition(!_ranges.isEmpty)
precondition(range.lowerBound <= _ranges.last!.upperBound)
precondition(range.upperBound >= _ranges.first!.lowerBound)
// The beginning index for the position of `range` is the first range
// with an upper bound larger than `range`'s lower bound. The range
// at this position may or may not overlap `range`.
let beginningIndex = _ranges
._partitioningIndex { $0.upperBound >= range.lowerBound }
// The ending index for `range` is the first range with a lower bound
// greater than `range`'s upper bound. If this is the same as
// `beginningIndex`, than `range` doesn't overlap any of the existing
// ranges. If this is `ranges.endIndex`, then `range` overlaps the
// rest of the ranges. Otherwise, `range` overlaps one or
// more ranges in the set.
let endingIndex = _ranges[beginningIndex...]
._partitioningIndex { $0.lowerBound > range.upperBound }
return beginningIndex ..< endingIndex
}
/// Inserts a non-empty range that is known to be greater than all the
/// elements in the set so far.
///
/// - Precondition: The range set must be empty, or else
/// `ranges.last!.upperBound <= range.lowerBound`.
/// - Precondition: `range` must not be empty.
internal mutating func _append(_ range: Range<Bound>) {
precondition(_ranges.isEmpty
|| _ranges.last!.upperBound <= range.lowerBound)
precondition(!range.isEmpty)
if _ranges.isEmpty {
_ranges.append(range)
} else if _ranges.last!.upperBound == range.lowerBound {
_ranges[_ranges.count - 1] =
_ranges[_ranges.count - 1].lowerBound ..< range.upperBound
} else {
_ranges.append(range)
}
}
/// Inserts the given range into the range set.
///
/// - Parameter range: The range to insert into the set.
///
/// - Complexity: O(*n*), where *n* is the number of ranges in the range
/// set.
public mutating func insert(contentsOf range: Range<Bound>) {
// Shortcuts for the (literal) edge cases
if range.isEmpty { return }
guard !_ranges.isEmpty else {
_ranges.append(range)
return
}
guard range.lowerBound < _ranges.last!.upperBound else {
_append(range)
return
}
guard range.upperBound >= _ranges.first!.lowerBound else {
_ranges.insert(range, at: 0)
return
}
let indices = _indicesOfRange(range)
// Non-overlapping is a simple insertion.
guard !indices.isEmpty else {
_ranges.insert(range, at: indices.lowerBound)
return
}
// Find the lower and upper bounds of the overlapping ranges.
let newLowerBound = Swift.min(
_ranges[indices.lowerBound].lowerBound,
range.lowerBound)
let newUpperBound = Swift.max(
_ranges[indices.upperBound - 1].upperBound,
range.upperBound)
_ranges.replaceSubrange(
indices,
with: CollectionOfOne(newLowerBound..<newUpperBound))
}
/// Removes the given range from the range set.
///
/// - Parameter range: The range to remove from the set.
///
/// - Complexity: O(*n*), where *n* is the number of ranges in the range
/// set.
public mutating func remove(contentsOf range: Range<Bound>) {
// Shortcuts for the (literal) edge cases
if range.isEmpty
|| _ranges.isEmpty
|| range.lowerBound >= _ranges.last!.upperBound
|| range.upperBound < _ranges.first!.lowerBound
{ return }
let indices = _indicesOfRange(range)
// No actual overlap, nothing to remove.
if indices.isEmpty { return }
let overlapsLowerBound =
range.lowerBound > _ranges[indices.lowerBound].lowerBound
let overlapsUpperBound =
range.upperBound < _ranges[indices.upperBound - 1].upperBound
switch (overlapsLowerBound, overlapsUpperBound) {
case (false, false):
_ranges.removeSubrange(indices)
case (false, true):
let newRange =
range.upperBound..<_ranges[indices.upperBound - 1].upperBound
_ranges.replaceSubrange(indices, with: CollectionOfOne(newRange))
case (true, false):
let newRange = _ranges[indices.lowerBound].lowerBound..<range.lowerBound
_ranges.replaceSubrange(indices, with: CollectionOfOne(newRange))
case (true, true):
_ranges.replaceSubrange(indices, with: Pair(
_ranges[indices.lowerBound].lowerBound..<range.lowerBound,
range.upperBound..<_ranges[indices.upperBound - 1].upperBound
))
}
}
}
extension RangeSet: Equatable {}
extension RangeSet: Hashable where Bound: Hashable {}
// MARK: - Range Collection
extension RangeSet {
/// A collection of the ranges that make up a range set.
public struct Ranges: RandomAccessCollection {
var _ranges: _RangeSetStorage<Bound>
public var startIndex: Int { _ranges.startIndex }
public var endIndex: Int { _ranges.endIndex }
public subscript(i: Int) -> Range<Bound> {
_ranges[i]
}
}
/// A collection of the ranges that make up the range set.
///
/// The ranges that you access by using `ranges` never overlap, are never
/// empty, and are always in increasing order.
public var ranges: Ranges {
Ranges(_ranges: _ranges)
}
}
// MARK: - Collection APIs
extension RangeSet {
/// Creates a new range set containing ranges that contain only the
/// specified indices in the given collection.
///
/// - Parameters:
/// - index: The index to include in the range set. `index` must be a
/// valid index of `collection` that isn't the collection's `endIndex`.
/// - collection: The collection that contains `index`.
public init<S, C>(_ indices: S, within collection: C)
where S: Sequence, C: Collection, S.Element == C.Index, C.Index == Bound
{
for i in indices {
self.insert(i, within: collection)
}
}
/// Inserts a range that contains only the specified index into the range
/// set.
///
/// - Parameters:
/// - index: The index to insert into the range set. `index` must be a
/// valid index of `collection` that isn't the collection's `endIndex`.
/// - collection: The collection that contains `index`.
///
/// - Complexity: O(*n*), where *n* is the number of ranges in the range
/// set.
public mutating func insert<C>(_ index: Bound, within collection: C)
where C: Collection, C.Index == Bound
{
insert(contentsOf: index ..< collection.index(after: index))
}
/// Removes the range that contains only the specified index from the range
/// set.
///
/// - Parameters:
/// - index: The index to remove from the range set. `index` must be a
/// valid index of `collection` that isn't the collection's `endIndex`.
/// - collection: The collection that contains `index`.
///
/// - Complexity: O(*n*), where *n* is the number of ranges in the range
/// set.
public mutating func remove<C>(_ index: Bound, within collection: C)
where C: Collection, C.Index == Bound
{
remove(contentsOf: index ..< collection.index(after: index))
}
/// Returns a range set that represents all the elements in the given
/// collection that aren't represented by this range set.
///
/// - Parameter collection: The collection that the range set is relative
/// to.
/// - Returns: A new range set that represents the elements in `collection`
/// that aren't represented by this range set.
///
/// - Complexity: O(*n*), where *n* is the number of ranges in the range
/// set.
internal func _inverted<C>(within collection: C) -> RangeSet
where C: Collection, C.Index == Bound
{
return _gaps(
boundedBy: collection.startIndex..<collection.endIndex)
}
/// Returns a range set that represents the ranges of values within the
/// given bounds that aren't represented by this range set.
internal func _gaps(boundedBy bounds: Range<Bound>) -> RangeSet {
guard !_ranges.isEmpty else { return RangeSet(bounds) }
guard let start = _ranges.firstIndex(where: { $0.lowerBound >= bounds.lowerBound })
else { return RangeSet() }
guard let end = _ranges.lastIndex(where: { $0.upperBound <= bounds.upperBound })
else { return RangeSet() }
var result = RangeSet()
var low = bounds.lowerBound
for range in _ranges[start...end] {
result.insert(contentsOf: low..<range.lowerBound)
low = range.upperBound
}
result.insert(contentsOf: low..<bounds.upperBound)
return result
}
}
// MARK: - SetAlgebra
// These methods only depend on the ranges that comprise the range set, so
// we can provide them even when we can't provide `SetAlgebra` conformance.
extension RangeSet {
/// Adds the contents of the given range set to this range set.
///
/// - Parameter other: A range set to merge with this one.
public mutating func formUnion(_ other: __owned RangeSet<Bound>) {
for range in other._ranges {
insert(contentsOf: range)
}
}
/// Removes the contents of this range set that aren't also in the given
/// range set.
///
/// - Parameter other: A range set to intersect with.
public mutating func formIntersection(_ other: RangeSet<Bound>) {
self = self.intersection(other)
}
/// Removes the contents of this range set that are also in the given set
/// and adds the contents of the given set that are not already in this
/// range set.
///
/// - Parameter other: A range set to perform a symmetric difference against.
public mutating func formSymmetricDifference(
_ other: __owned RangeSet<Bound>
) {
self = self.symmetricDifference(other)
}
/// Removes the contents of the given range set from this range set.
///
/// - Parameter other: A range set to subtract from this one.
public mutating func subtract(_ other: RangeSet<Bound>) {
for range in other._ranges {
remove(contentsOf: range)
}
}
/// Returns a new range set containing the contents of both this set and the
/// given set.
///
/// - Parameter other: The range set to merge with this one.
/// - Returns: A new range set.
public __consuming func union(
_ other: __owned RangeSet<Bound>
) -> RangeSet<Bound> {
var result = self
result.formUnion(other)
return result
}
/// Returns a new range set containing the contents of both this set and the
/// given set.
///
/// - Parameter other: The range set to merge with this one.
/// - Returns: A new range set.
public __consuming func intersection(
_ other: RangeSet<Bound>
) -> RangeSet<Bound> {
var otherRangeIndex = 0
var result: [Range<Bound>] = []
// Considering these two range sets:
//
// self = [0..<5, 9..<14]
// other = [1..<3, 4..<6, 8..<12]
//
// `self.intersection(other)` looks like this, where x's cover the
// ranges in `self`, y's cover the ranges in `other`, and z's cover the
// resulting ranges:
//
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// xxxxxxxxxxxxxxxxxxx__ xxxxxxxxxxxxxxxxxxx__
// yyyyyyy__ yyyyyyy__ yyyyyyyyyyyyyyy__
// zzzzzzz__ zzz__ zzzzzzzzzzz__
//
// The same, but for `other.intersection(self)`:
//
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// xxxxxxx__ xxxxxxx__ xxxxxxxxxxxxxxx__
// yyyyyyyyyyyyyyyyyyy__ yyyyyyyyyyyyyyyyyyy__
// zzzzzzz__ zzz__ zzzzzzzzzzz__
for currentRange in _ranges {
// Search forward in `other` until finding either an overlapping
// range or one that is strictly higher than this range.
while otherRangeIndex < other._ranges.endIndex &&
other._ranges[otherRangeIndex].upperBound <= currentRange.lowerBound
{
otherRangeIndex += 1
}
// For each range in `other` that overlaps with the current range
// in `self`, append the intersection to the result.
while otherRangeIndex < other._ranges.endIndex &&
other._ranges[otherRangeIndex].lowerBound < currentRange.upperBound
{
let lower = Swift.max(
other._ranges[otherRangeIndex].lowerBound,
currentRange.lowerBound)
let upper = Swift.min(
other._ranges[otherRangeIndex].upperBound,
currentRange.upperBound)
result.append(lower..<upper)
// If the range in `other` continues past the current range in
// `self`, it could overlap the next range in `self`, so break
// out of examining the current range.
guard
currentRange.upperBound > other._ranges[otherRangeIndex].upperBound
else {
break
}
otherRangeIndex += 1
}
}
return RangeSet(_orderedRanges: result)
}
/// Returns a new range set representing the values in this range set or the
/// given range set, but not both.
///
/// - Parameter other: The range set to find a symmetric difference with.
/// - Returns: A new range set.
public __consuming func symmetricDifference(
_ other: __owned RangeSet<Bound>
) -> RangeSet<Bound> {
return union(other).subtracting(intersection(other))
}
/// Returns a new set containing the contents of this range set that are not
/// also in the given range set.
///
/// - Parameter other: The range set to subtract.
/// - Returns: A new range set.
public func subtracting(_ other: RangeSet<Bound>) -> RangeSet<Bound> {
var result = self
result.subtract(other)
return result
}
/// Returns a Boolean value that indicates whether this range set is a
/// subset of the given set.
///
/// - Parameter other: A range set to compare against.
/// - Returns: `true` if this range set is a subset of `other`;
/// otherwise, `false`.
public func isSubset(of other: RangeSet<Bound>) -> Bool {
self.intersection(other) == self
}
/// Returns a Boolean value that indicates whether this range set is a
/// superset of the given set.
///
/// - Parameter other: A range set to compare against.
/// - Returns: `true` if this range set is a superset of `other`;
/// otherwise, `false`.
public func isSuperset(of other: RangeSet<Bound>) -> Bool {
other.isSubset(of: self)
}
/// Returns a Boolean value that indicates whether this range set is a
/// strict subset of the given set.
///
/// - Parameter other: A range set to compare against.
/// - Returns: `true` if this range set is a strict subset of `other`;
/// otherwise, `false`.
public func isStrictSubset(of other: RangeSet<Bound>) -> Bool {
self != other && isSubset(of: other)
}
/// Returns a Boolean value that indicates whether this range set is a
/// strict superset of the given set.
///
/// - Parameter other: A range set to compare against.
/// - Returns: `true` if this range set is a strict superset of `other`;
/// otherwise, `false`.
public func isStrictSuperset(of other: RangeSet<Bound>) -> Bool {
other.isStrictSubset(of: self)
}
}
extension RangeSet: CustomStringConvertible {
public var description: String {
let rangesDescription = _ranges
.map { r in "\(r.lowerBound)..<\(r.upperBound)" }
.joined(separator: ", ")
return "RangeSet(\(rangesDescription))"
}
}
@@ -0,0 +1,181 @@
//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
struct _RangeSetStorage<T: Comparable> {
fileprivate enum _Storage {
case empty
case singleRange(high: Int, low: Int32)
case variadic([Range<T>])
}
fileprivate var _storage: _Storage
init() {
_storage = .empty
}
init(_ range: Range<T>) {
if let intRange = range as? Range<Int>,
let lowerBound = Int32(exactly: intRange.lowerBound)
{
_storage = .singleRange(high: intRange.upperBound, low: lowerBound)
} else {
_storage = .variadic([range])
}
}
init(_ ranges: [Range<T>]) {
_storage = .variadic(ranges)
}
func unsafeRange(low: Int32, high: Int) -> Range<T> {
unsafeBitCast(Int(low)..<high, to: Range<T>.self)
}
}
// _RangeSetStorage has custom Equatable (and therefore Hashable)
// conformance, since the same "value" can be represented by different
// storage structures. For example, `.empty` and `.variadic([])` are
// equivalent, but the synthesized conformance treats them as distinct.
// The same holds with the `singleRange` representation and `variadic`
// with a single-element array.
extension _RangeSetStorage: Equatable {
static func == (lhs: _RangeSetStorage, rhs: _RangeSetStorage) -> Bool {
switch (lhs._storage, rhs._storage) {
case (.empty, .empty):
return true
case (.empty, .singleRange), (.singleRange, .empty):
return false
case let (.empty, .variadic(ranges)),
let (.variadic(ranges), .empty):
return ranges.isEmpty
case let (.singleRange(lhsHigh, lhsLow), .singleRange(rhsHigh, rhsLow)):
return (lhsLow, lhsHigh) == (rhsLow, rhsHigh)
case let (.singleRange(high, low), .variadic(ranges)),
let (.variadic(ranges), .singleRange(high, low)):
return ranges.count == 1 &&
(ranges[0] as! Range<Int>) == Int(low)..<high
case let (.variadic(lhsRanges), .variadic(rhsRanges)):
return lhsRanges == rhsRanges
}
}
}
extension _RangeSetStorage: Hashable where T: Hashable {
func hash(into hasher: inout Hasher) {
for range in self {
hasher.combine(range)
}
}
}
extension _RangeSetStorage: RandomAccessCollection, MutableCollection {
var startIndex: Int { 0 }
var endIndex: Int {
switch _storage {
case .empty: return 0
case .singleRange: return 1
case let .variadic(ranges): return ranges.count
}
}
subscript(i: Int) -> Range<T> {
get {
switch _storage {
case .empty: fatalError("Can't access elements of empty storage")
case let .singleRange(high, low):
assert(T.self == Int.self)
return unsafeRange(low: low, high: high)
case let .variadic(ranges):
return ranges[i]
}
}
set {
switch _storage {
case .empty: fatalError("Can't access elements of empty storage")
case .singleRange:
assert(T.self == Int.self)
let intRange = newValue as! Range<Int>
if let lowerBound = Int32(exactly: intRange.lowerBound) {
_storage = .singleRange(high: intRange.upperBound, low: lowerBound)
} else {
_storage = .variadic([newValue])
}
case .variadic(var ranges):
// Temporarily set `_storage` to empty so that `ranges`
// remains uniquely referenced while mutating.
_storage = .empty
ranges[i] = newValue
_storage = .variadic(ranges)
}
}
}
var count: Int {
switch _storage {
case .empty: return 0
case .singleRange: return 1
case let .variadic(ranges): return ranges.count
}
}
}
extension _RangeSetStorage: RangeReplaceableCollection {
mutating func replaceSubrange<C>(_ subrange: Range<Int>, with newElements: C) where C : Collection, C.Element == Element {
switch _storage {
case .empty:
if !newElements.isEmpty {
_storage = .variadic(Array(newElements))
}
case .singleRange(high: let high, low: let low):
switch (subrange.isEmpty, newElements.isEmpty) {
case (false, true):
// Replacing the single range with an empty collection.
_storage = .empty
case (false, false):
// Replacing the single range with a non-empty collection;
// promote to a variadic container.
_storage = .variadic(Array(newElements))
case (true, true):
// Inserting an empty collection; no-op.
break
case (true, false):
// Inserting a non-empty collection either before or after
// the existing single element.
var ranges: [Range<T>]
if subrange.lowerBound == 0 {
ranges = Array(newElements)
ranges.append(unsafeRange(low: low, high: high))
} else {
ranges = [unsafeRange(low: low, high: high)]
ranges.append(contentsOf: newElements)
}
_storage = .variadic(ranges)
}
case .variadic(var ranges):
// Temporarily set `_storage` to empty so that `ranges`
// remains uniquely referenced while mutating.
_storage = .empty
ranges.replaceSubrange(subrange, with: newElements)
_storage = .variadic(ranges)
}
}
}
+25
View File
@@ -0,0 +1,25 @@
objc_library(
name = "ShelfPack",
enable_modules = True,
module_name = "ShelfPack",
srcs = glob([
"Sources/**/*.m",
"Sources/**/*.mm",
"Sources/**/*.h",
"Sources/**/*.hpp",
"Sources/**/*.cpp",
], allow_empty=True),
hdrs = glob([
"PublicHeaders/**/*.h",
]),
includes = [
"PublicHeaders",
],
sdk_frameworks = [
"Foundation",
],
visibility = [
"//visibility:public",
],
)
+29
View File
@@ -0,0 +1,29 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "ShelfPack",
platforms: [.macOS(.v10_13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "ShelfPack",
targets: ["ShelfPack"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "ShelfPack",
dependencies: [],
path: ".",
publicHeadersPath: "PublicHeaders"),
],
cxxLanguageStandard: .cxx20
)
@@ -0,0 +1,33 @@
#ifndef ShelfPack_h
#define ShelfPack_h
#import <Foundation/Foundation.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
int32_t itemId;
int32_t x;
int32_t y;
int32_t width;
int32_t height;
} ShelfPackItem;
@interface ShelfPackContext : NSObject
@property (nonatomic, readonly) bool isEmpty;
- (instancetype _Nonnull)initWithWidth:(int32_t)width height:(int32_t)height;
- (ShelfPackItem)addItemWithWidth:(int32_t)width height:(int32_t)height;
- (void)removeItem:(int32_t)itemId;
@end
#ifdef __cplusplus
}
#endif
#endif /* ShelfPack_h */
@@ -0,0 +1,58 @@
#import <ShelfPack/ShelfPack.h>
#import "shelf-pack.hpp"
#import <memory>
@interface ShelfPackContext () {
std::unique_ptr<mapbox::ShelfPack> _pack;
int32_t _nextItemId;
int _count;
}
@end
@implementation ShelfPackContext
- (instancetype _Nonnull)initWithWidth:(int32_t)width height:(int32_t)height {
self = [super init];
if (self != nil) {
_pack = std::make_unique<mapbox::ShelfPack>(width, height);
}
return self;
}
- (bool)isEmpty {
return _count == 0;
}
- (ShelfPackItem)addItemWithWidth:(int32_t)width height:(int32_t)height {
ShelfPackItem item = {
.itemId = -1,
.x = 0,
.y = 0,
.width = 0,
.height = 0
};
int32_t itemId = _nextItemId;
_nextItemId += 1;
if (const auto bin = _pack->packOne(itemId, width, height)) {
item.itemId = bin->id;
item.x = bin->x;
item.y = bin->y;
item.width = bin->w;
item.height = bin->h;
_count += 1;
}
return item;
}
- (void)removeItem:(int32_t)itemId {
if (const auto bin = _pack->getBin(itemId)) {
_pack->unref(*bin);
_count -= 1;
}
}
@end
@@ -0,0 +1,535 @@
#ifndef SHELF_PACK_HPP
#define SHELF_PACK_HPP
#include <algorithm>
#include <cstdint>
#include <deque>
#include <limits>
#include <map>
#include <vector>
namespace mapbox {
const char * const SHELF_PACK_VERSION = "2.1.1";
class Bin {
friend class ShelfPack;
public:
/**
* Create a new Bin.
*
* @class Bin
* @param {int32_t} id Unique bin identifier
* @param {int32_t} [w1=-1] Width of the new Bin
* @param {int32_t} [h1=-1] Height of the new Bin
* @param {int32_t} [maxw1=-1] Maximum Width of the new Bin
* @param {int32_t} [maxh1=-1] Maximum Height of the new Bin
* @param {int32_t} [x1=-1] X location of the Bin
* @param {int32_t} [y1=-1] Y location of the Bin
*
* @example
* Bin b(-1, 12, 16);
*/
explicit Bin(
int32_t id1 = -1,
int32_t w1 = -1,
int32_t h1 = -1,
int32_t maxw1 = -1,
int32_t maxh1 = -1,
int32_t x1 = -1,
int32_t y1 = -1
) : id(id1), w(w1), h(h1), maxw(maxw1), maxh(maxh1), x(x1), y(y1), refcount_(0) {
if (maxw == -1) {
maxw = w;
}
if (maxh == -1) {
maxh = h;
}
}
int32_t id;
int32_t w;
int32_t h;
int32_t maxw;
int32_t maxh;
int32_t x;
int32_t y;
int32_t refcount() const { return refcount_; }
private:
int32_t refcount_;
};
class Shelf {
public:
/**
* Create a new Shelf.
*
* @class Shelf
* @param {int32_t} y1 Top coordinate of the new shelf
* @param {int32_t} w1 Width of the new shelf
* @param {int32_t} h1 Height of the new shelf
*
* @example
* Shelf shelf(64, 512, 24);
*/
explicit Shelf(int32_t y1, int32_t w1, int32_t h1) :
x_(0), y_(y1), w_(w1), h_(h1), wfree_(w1) { }
/**
* Allocate a single bin into the shelf.
* Bin is stored in a `bins_` container.
* Returned pointer is stable until the shelf is destroyed.
*
* @param {int32_t} id Unique bin identifier, pass -1 to generate a new one
* @param {int32_t} w1 Width of the bin to allocate
* @param {int32_t} h1 Height of the bin to allocate
* @returns {Bin*} `Bin` pointer with `id`, `x`, `y`, `w`, `h` members
*
* @example
* Bin* result = shelf.alloc(-1, 12, 16);
*/
Bin* alloc(int32_t id, int32_t w1, int32_t h1) {
if (w1 > wfree_ || h1 > h_) {
return nullptr;
}
int32_t x1 = x_;
x_ += w1;
wfree_ -= w1;
bins_.emplace_back(id, w1, h1, w1, h_, x1, y_);
return &bins_.back();
}
/**
* Resize the shelf.
*
* @param {int32_t} w1 Requested new width of the shelf
* @returns {bool} `true` if resize succeeded, `false` if failed
*
* @example
* shelf.resize(512);
*/
bool resize(int32_t w1) {
wfree_ += (w1 - w_);
w_ = w1;
return true;
}
int32_t x() const { return x_; }
int32_t y() const { return y_; }
int32_t w() const { return w_; }
int32_t h() const { return h_; }
int32_t wfree() const { return wfree_; }
private:
int32_t x_;
int32_t y_;
int32_t w_;
int32_t h_;
int32_t wfree_;
std::deque<Bin> bins_;
};
class ShelfPack {
public:
struct ShelfPackOptions {
inline ShelfPackOptions() : autoResize(false) { };
bool autoResize;
};
struct PackOptions {
inline PackOptions() : inPlace(false) { };
bool inPlace;
};
/**
* Create a new ShelfPack bin allocator.
*
* Uses the Shelf Best Height Fit algorithm from
* http://clb.demon.fi/files/RectangleBinPack.pdf
*
* @class ShelfPack
* @param {int32_t} [w=64] Initial width of the sprite
* @param {int32_t} [h=64] Initial width of the sprite
* @param {ShelfPackOptions} [options]
* @param {bool} [options.autoResize=false] If `true`, the sprite will automatically grow
*
* @example
* ShelfPack::ShelfPackOptions options;
* options.autoResize = false;
* ShelfPack sprite = new ShelfPack(64, 64, options);
*/
explicit ShelfPack(int32_t w = 0, int32_t h = 0, const ShelfPackOptions &options = ShelfPackOptions{}) {
width_ = w > 0 ? w : 64;
height_ = h > 0 ? h : 64;
autoResize_ = options.autoResize;
maxId_ = 0;
}
/**
* Batch pack multiple bins into the sprite.
*
* @param {vector<Bin>} bins Array of requested bins - each object should have `w`, `h` values
* @param {PackOptions} [options]
* @param {bool} [options.inPlace=false] If `true`, the supplied bin objects will be updated inplace with `x` and `y` values
* @returns {vector<Bin*>} Array of Bin pointers - each bin is a struct with `x`, `y`, `w`, `h` values
*
* @example
* std::vector<Bin> moreBins;
* moreBins.emplace_back(-1, 12, 24);
* moreBins.emplace_back(-1, 12, 12);
* moreBins.emplace_back(-1, 10, 10);
*
* ShelfPack::PackOptions options;
* options.inPlace = true;
* std::vector<Bin*> results = sprite.pack(moreBins, options);
*/
std::vector<Bin*> pack(std::vector<Bin> &bins, const PackOptions &options = PackOptions{}) {
std::vector<Bin*> results;
for (auto& bin : bins) {
if (bin.w > 0 && bin.h > 0) {
Bin* allocation = packOne(bin.id, bin.w, bin.h);
if (!allocation) {
continue;
}
if (options.inPlace) {
bin.id = allocation->id;
bin.x = allocation->x;
bin.y = allocation->y;
}
results.push_back(allocation);
}
}
shrink();
return results;
}
/**
* Pack a single bin into the sprite.
*
* @param {int32_t} id Unique bin identifier, pass -1 to generate a new one
* @param {int32_t} w Width of the bin to allocate
* @param {int32_t} h Height of the bin to allocate
* @returns {Bin*} Pointer to a packed Bin with `id`, `x`, `y`, `w`, `h` members
*
* @example
* Bin* result = sprite.packOne(-1, 12, 16);
*/
Bin* packOne(int32_t id, int32_t w, int32_t h) {
int32_t y = 0;
int32_t waste = 0;
struct {
Shelf* pshelf = nullptr;
Bin* pfreebin = nullptr;
int32_t waste = std::numeric_limits<std::int32_t>::max();
} best;
// if id was supplied, attempt a lookup..
if (id != -1) {
Bin* pbin = getBin(id);
if (pbin) { // we packed this bin already
ref(*pbin);
return pbin;
}
maxId_ = std::max(id, maxId_);
} else {
id = ++maxId_;
}
// First try to reuse a free bin..
for (auto& freebin : freebins_) {
// exactly the right height and width, use it..
if (h == freebin->maxh && w == freebin->maxw) {
return allocFreebin(freebin, id, w, h);
}
// not enough height or width, skip it..
if (h > freebin->maxh || w > freebin->maxw) {
continue;
}
// extra height or width, minimize wasted area..
if (h <= freebin->maxh && w <= freebin->maxw) {
waste = (freebin->maxw * freebin->maxh) - (w * h);
if (waste < best.waste) {
best.waste = waste;
best.pfreebin = freebin;
}
}
}
// Next find the best shelf
for (auto& shelf : shelves_) {
y += shelf.h();
// not enough width on this shelf, skip it..
if (w > shelf.wfree()) {
continue;
}
// exactly the right height, pack it..
if (h == shelf.h()) {
return allocShelf(shelf, id, w, h);
}
// not enough height, skip it..
if (h > shelf.h()) {
continue;
}
// extra height, minimize wasted area..
if (h < shelf.h()) {
waste = (shelf.h() - h) * w;
if (waste < best.waste) {
best.waste = waste;
best.pshelf = &shelf;
}
}
}
if (best.pfreebin) {
return allocFreebin(best.pfreebin, id, w, h);
}
if (best.pshelf) {
return allocShelf(*best.pshelf, id, w, h);
}
// No free bins or shelves.. add shelf..
if (h <= (height_ - y) && w <= width_) {
shelves_.emplace_back(y, width_, h);
return allocShelf(shelves_.back(), id, w, h);
}
// No room for more shelves..
// If `autoResize` option is set, grow the sprite as follows:
// * double whichever sprite dimension is smaller (`w1` or `h1`)
// * if sprite dimensions are equal, grow width before height
// * accomodate very large bin requests (big `w` or `h`)
if (autoResize_) {
int32_t h1, h2, w1, w2;
h1 = h2 = height_;
w1 = w2 = width_;
if (w1 <= h1 || w > w1) { // grow width..
w2 = std::max(w, w1) * 2;
}
if (h1 < w1 || h > h1) { // grow height..
h2 = std::max(h, h1) * 2;
}
resize(w2, h2);
return packOne(id, w, h); // retry
}
return nullptr;
}
/**
*
* Shrink the width/height of the sprite to the bare minimum.
* Since shelf-pack doubles first width, then height when running out of shelf space
* this can result in fairly large unused space both in width and height if that happens
* towards the end of bin packing.
*/
void shrink() {
if (shelves_.size()) {
int32_t w2 = 0;
int32_t h2 = 0;
for (auto& shelf : shelves_) {
h2 += shelf.h();
w2 = std::max(shelf.w() - shelf.wfree(), w2);
}
resize(w2, h2);
}
}
/**
* Return a packed bin given its id, or nullptr if the id is not found
*
* @param {int32_t} id Unique identifier for this bin,
* @returns {Bin*} Pointer to a packed Bin with `id`, `x`, `y`, `w`, `h` members
*
* @example
* Bin* result = sprite.getBin(5);
*/
Bin* getBin(int32_t id) {
std::map<int32_t, Bin*>::iterator it = usedbins_.find(id);
return (it == usedbins_.end()) ? nullptr : it->second;
}
/**
* Increment the ref count of a bin and update statistics.
*
* @param {Bin&} bin Bin reference
* @returns {int32_t} New refcount of the bin
*
* @example
* Bin* bin = sprite.getBin(5);
* if (bin) {
* sprite.ref(*bin);
* }
*/
int32_t ref(Bin& bin) {
if (++bin.refcount_ == 1) { // a new Bin.. record height in stats historgram..
int32_t h = bin.h;
stats_[h] = (stats_[h] | 0) + 1;
}
return bin.refcount_;
};
/**
* Decrement the ref count of a bin and update statistics.
* The bin will be automatically marked as free space once the refcount reaches 0.
* Memory for the bin is not freed, as unreferenced bins may be reused later.
*
* @param {Bin&} bin Bin reference
* @returns {int32_t} New refcount of the bin
*
* @example
* Bin* bin = sprite.getBin(5);
* if (bin) {
* sprite.unref(*bin);
* }
*/
int32_t unref(Bin& bin) {
if (bin.refcount_ == 0) {
return 0;
}
if (--bin.refcount_ == 0) {
stats_[bin.h]--;
usedbins_.erase(bin.id);
freebins_.push_back(&bin);
}
return bin.refcount_;
}
/**
* Clear the sprite and reset statistics.
*
* @example
* sprite.clear();
*/
void clear() {
shelves_.clear();
freebins_.clear();
usedbins_.clear();
stats_.clear();
maxId_ = 0;
}
/**
* Resize the sprite.
*
* @param {int32_t} w Requested new sprite width
* @param {int32_t} h Requested new sprite height
* @returns {bool} `true` if resize succeeded, `false` if failed
*
* @example
* sprite.resize(256, 256);
*/
bool resize(int32_t w, int32_t h) {
width_ = w;
height_ = h;
for (auto& shelf : shelves_) {
shelf.resize(width_);
}
return true;
}
int32_t width() const { return width_; }
int32_t height() const { return height_; }
private:
/**
* Called by packOne() to allocate a bin by reusing an existing freebin
*
* @private
* @param {Bin*} bin Pointer to a freebin to reuse
* @param {int32_t} w Width of the bin to allocate
* @param {int32_t} h Height of the bin to allocate
* @param {int32_t} id Unique identifier for this bin
* @returns {Bin*} Pointer to a Bin with `id`, `x`, `y`, `w`, `h` properties
*
* @example
* Bin* bin = sprite.allocFreebin(pfreebin, 12, 16, 5);
*/
Bin* allocFreebin(Bin* bin, int32_t id, int32_t w, int32_t h) {
freebins_.erase(std::remove(freebins_.begin(), freebins_.end(), bin), freebins_.end());
bin->id = id;
bin->w = w;
bin->h = h;
bin->refcount_ = 0;
usedbins_[id] = bin;
ref(*bin);
return bin;
}
/**
* Called by `packOne() to allocate bin on an existing shelf
* Memory for the bin is allocated on the heap by `shelf.alloc()`
*
* @private
* @param {Shelf&} shelf Reference to the shelf to allocate the bin on
* @param {int32_t} w Width of the bin to allocate
* @param {int32_t} h Height of the bin to allocate
* @param {int32_t} id Unique identifier for this bin
* @returns {Bin*} Pointer to a Bin with `id`, `x`, `y`, `w`, `h` properties
*
* @example
* Bin* bin = sprite.allocShelf(shelf, 12, 16, 5);
*/
Bin* allocShelf(Shelf& shelf, int32_t id, int32_t w, int32_t h) {
Bin* pbin = shelf.alloc(id, w, h);
if (pbin) {
usedbins_[id] = pbin;
ref(*pbin);
}
return pbin;
}
int32_t width_;
int32_t height_;
int32_t maxId_;
bool autoResize_;
std::deque<Shelf> shelves_;
std::map<int32_t, Bin*> usedbins_;
std::vector<Bin*> freebins_;
std::map<int32_t, int32_t> stats_;
};
} // namespace mapbox
#endif
+20
View File
@@ -0,0 +1,20 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "VolumeButtons",
module_name = "VolumeButtons",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/LegacyComponents",
"//submodules/AccountContext",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,269 @@
import Foundation
import UIKit
import AVKit
import SwiftSignalKit
import MediaPlayer
import LegacyComponents
import AccountContext
private protocol VolumeButtonHandlerImpl {
}
private final class LegacyHandlerImpl: VolumeButtonHandlerImpl {
private let handler: PGCameraVolumeButtonHandler
init(
context: SharedAccountContext,
performAction: @escaping (VolumeButtonsListener.Action) -> Void
) {
self.handler = PGCameraVolumeButtonHandler(
isCameraSpecific: false,
eventView: context.mainWindow?.viewController?.view,
upButtonPressedBlock: {
performAction(.up)
}, upButtonReleasedBlock: {
performAction(.upRelease)
}, downButtonPressedBlock: {
performAction(.down)
}, downButtonReleasedBlock: {
performAction(.downRelease)
}
)
self.handler.enabled = true
}
deinit {
self.handler.enabled = false
}
}
@available(iOS 17.2, *)
private final class AVCaptureEventHandlerImpl: VolumeButtonHandlerImpl {
private weak var context: SharedAccountContext?
private let interaction: AVCaptureEventInteraction
init(
context: SharedAccountContext,
performAction: @escaping (VolumeButtonsListener.Action) -> Void
) {
self.context = context
self.interaction = AVCaptureEventInteraction(
primary: { event in
switch event.phase {
case .began:
performAction(.down)
case .ended:
performAction(.downRelease)
case .cancelled:
performAction(.downRelease)
@unknown default:
break
}
},
secondary: { event in
switch event.phase {
case .began:
performAction(.up)
case .ended:
performAction(.upRelease)
case .cancelled:
performAction(.upRelease)
@unknown default:
break
}
}
)
self.interaction.isEnabled = true
context.mainWindow?.viewController?.view.addInteraction(self.interaction)
}
deinit {
self.interaction.isEnabled = false
self.context?.mainWindow?.viewController?.view.removeInteraction(self.interaction)
}
}
public class VolumeButtonsListener {
private final class ListenerReference {
let id: Int
weak var listener: VolumeButtonsListener?
init(id: Int, listener: VolumeButtonsListener) {
self.id = id
self.listener = listener
}
}
fileprivate enum Action {
case up
case upRelease
case down
case downRelease
}
private final class SharedContext: NSObject {
private var handler: VolumeButtonHandlerImpl?
private var cameraSpecificHandler: VolumeButtonHandlerImpl?
private weak var sharedAccountContext: SharedAccountContext?
private var nextListenerId: Int = 0
private var listeners: [ListenerReference] = []
override init() {
super.init()
}
func add(listener: VolumeButtonsListener) -> Int {
self.sharedAccountContext = listener.sharedAccountContext
let id = self.nextListenerId
self.nextListenerId += 1
self.listeners.append(ListenerReference(id: id, listener: listener))
self.updateListeners()
return id
}
func update(id: Int) {
self.updateListeners()
}
func remove(id: Int) {
if let index = self.listeners.firstIndex(where: { $0.id == id }) {
self.listeners.remove(at: index)
self.updateListeners()
}
}
private func performAction(_ action: Action, isCameraSpecific: Bool) {
for i in (0 ..< self.listeners.count).reversed() {
if let listener = self.listeners[i].listener, listener.isActive, listener.isCameraSpecific == isCameraSpecific {
switch action {
case .up:
listener.upPressed()
case .upRelease:
listener.upReleased()
case .down:
listener.downPressed()
case .downRelease:
listener.downReleased()
}
}
}
}
private func updateListeners() {
var isGeneralActive = false
var isCameraSpecificActive = false
for i in (0 ..< self.listeners.count).reversed() {
if let listener = self.listeners[i].listener {
if listener.isActive {
if #available(iOS 17.2, *) {
if listener.isCameraSpecific {
isCameraSpecificActive = true
} else {
isGeneralActive = true
}
} else {
isGeneralActive = true
}
}
} else {
self.listeners.remove(at: i)
}
}
if isGeneralActive {
if self.handler == nil {
if let sharedAccountContext = self.sharedAccountContext {
let performAction: (VolumeButtonsListener.Action) -> Void = { [weak self] action in
self?.performAction(action, isCameraSpecific: false)
}
self.handler = LegacyHandlerImpl(
context: sharedAccountContext,
performAction: performAction
)
}
}
} else {
self.handler = nil
}
if isCameraSpecificActive {
if self.cameraSpecificHandler == nil {
if let sharedAccountContext = self.sharedAccountContext {
let performAction: (VolumeButtonsListener.Action) -> Void = { [weak self] action in
self?.performAction(action, isCameraSpecific: true)
}
if #available(iOS 17.2, *) {
self.cameraSpecificHandler = AVCaptureEventHandlerImpl(
context: sharedAccountContext,
performAction: performAction
)
} else {
self.cameraSpecificHandler = nil
}
}
}
} else {
self.cameraSpecificHandler = nil
}
}
}
fileprivate let sharedAccountContext: SharedAccountContext
fileprivate let isCameraSpecific: Bool
private static var sharedContext: SharedContext = {
return SharedContext()
}()
fileprivate let upPressed: () -> Void
fileprivate let upReleased: () -> Void
fileprivate let downPressed: () -> Void
fileprivate let downReleased: () -> Void
private var index: Int?
fileprivate var isActive: Bool = false
private var disposable: Disposable?
public init(
sharedContext: SharedAccountContext,
isCameraSpecific: Bool,
shouldBeActive: Signal<Bool, NoError>,
upPressed: @escaping () -> Void,
upReleased: @escaping () -> Void = {},
downPressed: @escaping () -> Void,
downReleased: @escaping () -> Void = {}
) {
self.sharedAccountContext = sharedContext
self.isCameraSpecific = isCameraSpecific
self.upPressed = upPressed
self.upReleased = upReleased
self.downPressed = downPressed
self.downReleased = downReleased
self.index = VolumeButtonsListener.sharedContext.add(listener: self)
self.disposable = (shouldBeActive
|> distinctUntilChanged
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let self, let index = self.index else {
return
}
self.isActive = value
VolumeButtonsListener.sharedContext.update(id: index)
})
}
deinit {
if let index = self.index {
VolumeButtonsListener.sharedContext.remove(id: index)
}
self.disposable?.dispose()
}
}