1 // Copyright 2022-present 650 Industries. All rights reserved. 2 3 /** 4 Type of the IDs of shared objects. 5 */ 6 public typealias SharedObjectId = Int 7 8 /** 9 A tuple containing a pair of matching native and JS objects. 10 */ 11 internal typealias SharedObjectPair = (native: SharedObject, javaScript: JavaScriptWeakObject) 12 13 /** 14 Property name of the JS object where the shared object ID is stored. 15 */ 16 let sharedObjectIdPropertyName = "__expo_shared_object_id__" 17 18 /** 19 The registry of all shared objects used in the entire app. 20 It's been made static for simplicity. 21 */ 22 public final class SharedObjectRegistry { 23 /** 24 The counter of IDs to assign to the shared object pairs. 25 The next pair added to the registry will be saved using this ID. 26 */ 27 internal static var nextId: SharedObjectId = 1 28 29 /** 30 A dictionary of shared object pairs. 31 */ 32 internal static var pairs = [SharedObjectId: SharedObjectPair]() 33 34 /** 35 A number of all pairs stored in the registry. 36 */ 37 internal static var size: Int { 38 return pairs.count 39 } 40 41 /** 42 Returns the next shared object ID and increases the counter. 43 */ 44 @discardableResult 45 internal static func pullNextId() -> SharedObjectId { 46 let id = nextId 47 nextId += 1 48 return id 49 } 50 51 /** 52 Returns a pair of shared objects with given ID or `nil` when there is no such pair in the registry. 53 */ 54 internal static func get(_ id: SharedObjectId) -> SharedObjectPair? { 55 return pairs[id] 56 } 57 58 /** 59 Adds a pair of native and JS shared object to the registry. Assigns a new shared object ID to these objects. 60 */ 61 @discardableResult 62 internal static func add(native nativeObject: SharedObject, javaScript jsObject: JavaScriptObject) -> SharedObjectId { 63 let id = pullNextId() 64 65 // Assigns the ID to the objects. 66 nativeObject.sharedObjectId = id 67 jsObject.defineProperty(sharedObjectIdPropertyName, value: id, options: [.writable]) 68 69 // Set the deallocator on the JS object that deletes the entire pair. 70 jsObject.setObjectDeallocator { delete(id) } 71 72 // Save the pair in the dictionary. 73 pairs[id] = (native: nativeObject, javaScript: jsObject.createWeak()) 74 75 return id 76 } 77 78 /** 79 Deletes the shared objects pair with a given ID. 80 */ 81 internal static func delete(_ id: SharedObjectId) { 82 if let pair = pairs[id] { 83 // Reset an ID on the objects. 84 pair.native.sharedObjectId = 0 85 pair.javaScript.lock()?.defineProperty(sharedObjectIdPropertyName, value: 0, options: [.writable]) 86 87 // Delete the pair from the dictionary. 88 pairs[id] = nil 89 } 90 } 91 92 /** 93 Gets the native shared object that is paired with a given JS object. 94 */ 95 internal static func toNativeObject(_ jsObject: JavaScriptObject) -> SharedObject? { 96 if let objectId = try? jsObject.getProperty(sharedObjectIdPropertyName).asInt() { 97 return pairs[objectId]?.native 98 } 99 return nil 100 } 101 102 /** 103 Gets the JS shared object that is paired with a given native object. 104 */ 105 internal static func toJavaScriptObject(_ nativeObject: SharedObject) -> JavaScriptObject? { 106 let objectId = nativeObject.sharedObjectId 107 return pairs[objectId]?.javaScript.lock() 108 } 109 110 /** 111 Creates a plain JS object and pairs it with a given native object. 112 */ 113 internal static func createSharedJavaScriptObject(runtime: JavaScriptRuntime, nativeObject: SharedObject) -> JavaScriptObject { 114 let object = runtime.createObject() 115 add(native: nativeObject, javaScript: object) 116 return object 117 } 118 119 /** 120 Ensures that there is a JS object paired with a given native object. If not, a plain JS object is created. 121 */ 122 internal static func ensureSharedJavaScriptObject(runtime: JavaScriptRuntime, nativeObject: SharedObject) -> JavaScriptObject { 123 if let jsObject = toJavaScriptObject(nativeObject) { 124 // JS object for this native object already exists in the registry, just return it. 125 return jsObject 126 } 127 return createSharedJavaScriptObject(runtime: runtime, nativeObject: nativeObject) 128 } 129 130 internal static func clear() { 131 pairs.removeAll() 132 } 133 } 134