lecodes-sdk 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/README.md +5 -2
  2. package/dist/editor.d.ts +12 -0
  3. package/dist/global.d.ts +4 -8
  4. package/dist/host.d.ts +2 -3
  5. package/dist/types/animate/tween/Animation.d.ts +0 -3
  6. package/dist/types/animate/tween/animateValue.d.ts +4 -2
  7. package/dist/types/animate/tween/easing.d.ts +8 -0
  8. package/dist/types/animate/tween/spec.d.ts +15 -7
  9. package/dist/types/audio/audio.d.ts +2 -1
  10. package/dist/types/canvas/Canvas.d.ts +40 -108
  11. package/dist/types/canvas/gen/cssColor.d.ts +17 -0
  12. package/dist/types/canvas/gen/recorder.d.ts +118 -0
  13. package/dist/types/canvas/gen/spec.d.ts +144 -0
  14. package/dist/types/core/color.d.ts +3 -1
  15. package/dist/types/core/pins.d.ts +18 -0
  16. package/dist/types/g2/Node2D.d.ts +5 -8
  17. package/dist/types/g2/Scene2D.d.ts +6 -2
  18. package/dist/types/gl/Foliage.d.ts +30 -7
  19. package/dist/types/gl/Light.d.ts +11 -0
  20. package/dist/types/gl/Lightmap.d.ts +13 -2
  21. package/dist/types/gl/Material.d.ts +17 -3
  22. package/dist/types/gl/Model.d.ts +6 -2
  23. package/dist/types/gl/Node.d.ts +3 -6
  24. package/dist/types/gl/Particles.d.ts +15 -0
  25. package/dist/types/gl/Scene.d.ts +41 -13
  26. package/dist/types/gl/Texture.d.ts +1 -1
  27. package/dist/types/gl/animation/Locomotion.d.ts +59 -3
  28. package/dist/types/gl/terrain/Terrain.d.ts +8 -0
  29. package/dist/types/inject.d.ts +11 -11
  30. package/dist/types/inject.editor.d.ts +1 -0
  31. package/dist/types/net/core.d.ts +7 -0
  32. package/dist/types/plugin.d.ts +76 -0
  33. package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
  34. package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
  35. package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
  36. package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
  37. package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
  38. package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
  39. package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
  40. package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
  41. package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
  42. package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
  43. package/dist/types/runtime/app.d.ts +9 -2
  44. package/dist/types/runtime/fetch.d.ts +2 -0
  45. package/dist/types/runtime/input.d.ts +8 -1
  46. package/dist/types/runtime/media.d.ts +6 -10
  47. package/dist/types/runtime/misc.d.ts +4 -1
  48. package/dist/types/runtime/net.d.ts +3 -2
  49. package/dist/types/runtime/touch.d.ts +32 -0
  50. package/dist/types/scene/defineScene.d.ts +43 -2
  51. package/dist/types/scene/editor.d.ts +52 -0
  52. package/dist/types/scene/gizmos.d.ts +7 -4
  53. package/dist/types/ui/NativeView.d.ts +6 -4
  54. package/dist/types/ui/UI.d.ts +1 -1
  55. package/dist/types/ui/UIBottomSheet.d.ts +6 -12
  56. package/dist/types/ui/UIButton.d.ts +12 -14
  57. package/dist/types/ui/UIContainer.d.ts +0 -6
  58. package/dist/types/ui/UIImage.d.ts +1 -4
  59. package/dist/types/ui/UIInput.d.ts +8 -24
  60. package/dist/types/ui/UIModal.d.ts +0 -2
  61. package/dist/types/ui/UINode.d.ts +70 -56
  62. package/dist/types/ui/UIPager.d.ts +28 -27
  63. package/dist/types/ui/UIPopover.d.ts +0 -2
  64. package/dist/types/ui/UIScreen.d.ts +12 -17
  65. package/dist/types/ui/UIScrollable.d.ts +1 -4
  66. package/dist/types/ui/UIText.d.ts +0 -2
  67. package/dist/types/ui/UIVideo.d.ts +3 -5
  68. package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
  69. package/dist/types/ui/UIWidget.d.ts +10 -9
  70. package/dist/types/ui/colorKeys.gen.d.ts +9 -0
  71. package/dist/types/ui/presentable.d.ts +46 -32
  72. package/dist/types/ui/router.d.ts +18 -7
  73. package/dist/types/ui/styleColor.d.ts +1 -0
  74. package/dist/types/ui/transitions.d.ts +18 -0
  75. package/dist/types/ui/tree.d.ts +75 -0
  76. package/dist/types/version.d.ts +10 -0
  77. package/dist/types.json +1 -1
  78. package/package.json +12 -3
  79. package/prompts/2d.md +2 -6
  80. package/prompts/3d.md +1 -5
  81. package/prompts/README.md +142 -142
  82. package/prompts/canvas.md +9 -8
  83. package/prompts/compose.ts +1 -1
  84. package/prompts/core.md +3 -3
  85. package/prompts/design.md +1 -1
  86. package/prompts/dist/2d-game.md +77 -54
  87. package/prompts/dist/3d-app.md +75 -52
  88. package/prompts/dist/ar-app.md +74 -51
  89. package/prompts/dist/design.md +30 -8
  90. package/prompts/dist/ui-app.md +73 -46
  91. package/prompts/ui-design.md +2 -3
  92. package/prompts/ui.md +45 -37
  93. package/src/animate/tween/Animation.ts +262 -378
  94. package/src/animate/tween/animateValue.ts +103 -100
  95. package/src/animate/tween/easing.ts +10 -3
  96. package/src/animate/tween/spec.ts +505 -479
  97. package/src/audio/Sound.ts +3 -3
  98. package/src/audio/audio.ts +162 -161
  99. package/src/bridges/2d.d.ts +317 -0
  100. package/src/bridges/app.d.ts +91 -0
  101. package/src/bridges/audio.d.ts +97 -0
  102. package/src/bridges/canvas.d.ts +79 -0
  103. package/src/bridges/device.d.ts +72 -0
  104. package/src/bridges/fetch.d.ts +80 -0
  105. package/src/bridges/files.d.ts +70 -0
  106. package/src/bridges/gl.d.ts +1133 -0
  107. package/src/bridges/input.d.ts +72 -0
  108. package/src/bridges/media.d.ts +55 -0
  109. package/src/bridges/nav.d.ts +71 -0
  110. package/src/bridges/net.d.ts +52 -0
  111. package/src/bridges/service.d.ts +52 -0
  112. package/src/bridges/socket.d.ts +31 -0
  113. package/src/bridges/storage.d.ts +33 -0
  114. package/src/bridges/tree.d.ts +301 -0
  115. package/src/bridges/types.d.ts +49 -0
  116. package/src/canvas/Canvas.ts +114 -159
  117. package/src/canvas/gen/cssColor.ts +224 -0
  118. package/src/canvas/gen/recorder.ts +212 -0
  119. package/src/canvas/gen/spec.ts +201 -0
  120. package/src/chisel.ts +193 -0
  121. package/src/compile/assetMacro.ts +1 -1
  122. package/src/compile/bundler.ts +11 -2
  123. package/src/compile/compileProject.ts +43 -4
  124. package/src/compile/fontMacro.ts +3 -4
  125. package/src/compile/header.ts +26 -5
  126. package/src/compile/index.ts +3 -1
  127. package/src/compile/liteMaterial.ts +1 -1
  128. package/src/compile/sceneEditor.ts +11 -26
  129. package/src/core/color.ts +73 -30
  130. package/src/core/pins.ts +51 -0
  131. package/src/core/signals.ts +8 -1
  132. package/src/g2/CharacterController2D.ts +3 -3
  133. package/src/g2/Node2D.ts +57 -39
  134. package/src/g2/Physics2D.ts +2 -2
  135. package/src/g2/Scene2D.ts +35 -23
  136. package/src/g2/Texture2D.ts +1 -1
  137. package/src/g2/loop.ts +4 -4
  138. package/src/gl/Foliage.ts +72 -17
  139. package/src/gl/Geometry.ts +1 -2
  140. package/src/gl/Light.ts +13 -0
  141. package/src/gl/Lightmap.ts +45 -29
  142. package/src/gl/Material.ts +95 -51
  143. package/src/gl/Model.ts +172 -167
  144. package/src/gl/Node.ts +91 -24
  145. package/src/gl/Particles.ts +20 -1
  146. package/src/gl/Scene.ts +100 -44
  147. package/src/gl/Texture.ts +8 -7
  148. package/src/gl/animation/AnimationClip.ts +1 -1
  149. package/src/gl/animation/Locomotion.ts +77 -8
  150. package/src/gl/nav/NavMesh.ts +3 -4
  151. package/src/gl/physics/Physics.ts +2 -2
  152. package/src/gl/physics/physicsEvents.ts +3 -3
  153. package/src/gl/terrain/Terrain.ts +33 -5
  154. package/src/gl/touch.ts +14 -15
  155. package/src/host.d.ts +2 -3
  156. package/src/inject.editor.ts +7 -0
  157. package/src/inject.ts +233 -236
  158. package/src/net/core.ts +6 -5
  159. package/src/net/index.ts +1 -1
  160. package/src/net/replication.ts +1 -1
  161. package/src/plugin.ts +191 -0
  162. package/src/plugins/gen/camera/contract.d.ts +27 -0
  163. package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
  164. package/src/plugins/gen/camera/sdk/camera.ts +57 -0
  165. package/src/plugins/gen/geolocation/contract.d.ts +50 -0
  166. package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
  167. package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
  168. package/src/plugins/gen/map/contract.d.ts +144 -0
  169. package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
  170. package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
  171. package/src/plugins/gen/push/contract.d.ts +61 -0
  172. package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
  173. package/src/plugins/gen/push/sdk/push.ts +105 -0
  174. package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
  175. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
  176. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
  177. package/src/plugins/permission.ts +5 -4
  178. package/src/runtime/app.ts +20 -9
  179. package/src/runtime/appEvents.ts +5 -4
  180. package/src/runtime/channel.ts +18 -15
  181. package/src/runtime/clipboard.ts +4 -3
  182. package/src/runtime/datetime.ts +2 -1
  183. package/src/runtime/device.ts +17 -15
  184. package/src/runtime/fetch.ts +30 -20
  185. package/src/runtime/files.ts +16 -15
  186. package/src/runtime/input.ts +21 -8
  187. package/src/runtime/media.ts +50 -46
  188. package/src/runtime/misc.ts +7 -3
  189. package/src/runtime/net.ts +8 -7
  190. package/src/runtime/rpc.ts +1 -3
  191. package/src/runtime/service.ts +19 -14
  192. package/src/runtime/share.ts +4 -3
  193. package/src/runtime/storage.ts +6 -4
  194. package/src/runtime/touch.ts +32 -0
  195. package/src/scene/defineScene.ts +61 -365
  196. package/src/scene/editor.ts +408 -0
  197. package/src/scene/editorPlugins.ts +3 -3
  198. package/src/scene/gizmos.ts +154 -148
  199. package/src/server/db/marci/query.ts +1 -1
  200. package/src/server/host.ts +1 -1
  201. package/src/server/runtime.ts +1 -1
  202. package/src/ui/NativeView.ts +72 -25
  203. package/src/ui/UI.ts +3 -3
  204. package/src/ui/UIBottomSheet.ts +16 -17
  205. package/src/ui/UIButton.ts +54 -16
  206. package/src/ui/UIContainer.ts +0 -6
  207. package/src/ui/UIImage.ts +34 -30
  208. package/src/ui/UIInput.ts +29 -37
  209. package/src/ui/UIModal.ts +1 -3
  210. package/src/ui/UINode.ts +347 -297
  211. package/src/ui/UIPager.ts +93 -78
  212. package/src/ui/UIPopover.ts +0 -2
  213. package/src/ui/UIScreen.ts +41 -36
  214. package/src/ui/UIScrollable.ts +19 -13
  215. package/src/ui/UISpacer.ts +1 -1
  216. package/src/ui/UITabs.ts +8 -6
  217. package/src/ui/UIText.ts +7 -17
  218. package/src/ui/UIVideo.ts +30 -27
  219. package/src/ui/UIVirtualizedList.ts +58 -59
  220. package/src/ui/UIWidget.ts +38 -18
  221. package/src/ui/colorKeys.gen.ts +37 -0
  222. package/src/ui/fonts.ts +2 -2
  223. package/src/ui/presentable.ts +59 -42
  224. package/src/ui/router.ts +52 -35
  225. package/src/ui/styleColor.ts +56 -0
  226. package/src/ui/theme.ts +7 -6
  227. package/src/ui/transitions.ts +249 -0
  228. package/src/ui/tree.ts +346 -0
  229. package/src/version.ts +24 -0
  230. package/tests/helpers/engineWorld.ts +11 -0
  231. package/tests/helpers/fakeTree.ts +353 -0
  232. package/tests/helpers/hostStubs.ts +31 -0
  233. package/tests/helpers/index.ts +14 -0
  234. package/tests/helpers/memoryMarci.ts +124 -0
  235. package/tests/helpers/phases.ts +23 -0
  236. package/tests/helpers/preload.ts +18 -0
  237. package/tests/helpers/stubApp.ts +2 -0
  238. package/tests/helpers/stubDevice.ts +2 -0
  239. package/tests/helpers/stubFetch.ts +2 -0
  240. package/tests/helpers/stubInput.ts +2 -0
  241. package/dist/inject.js +0 -4730
  242. package/dist/types/core/registry.d.ts +0 -7
  243. package/dist/types/plugins/camera.d.ts +0 -25
  244. package/dist/types/plugins/push.d.ts +0 -46
  245. package/src/bridges.d.ts +0 -1760
  246. package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
  247. package/src/compile/__tests__/assetMacro.test.ts +0 -100
  248. package/src/compile/__tests__/assetName.test.ts +0 -55
  249. package/src/compile/__tests__/compile.test.ts +0 -310
  250. package/src/compile/__tests__/detectEntry.test.ts +0 -151
  251. package/src/compile/__tests__/fontMacro.test.ts +0 -199
  252. package/src/compile/__tests__/serverSplit.test.ts +0 -27
  253. package/src/core/__tests__/stateMachine.test.ts +0 -132
  254. package/src/core/registry.ts +0 -23
  255. package/src/plugins/camera.ts +0 -81
  256. package/src/plugins/push.ts +0 -132
  257. package/src/plugins/qr.ts +0 -73
@@ -1,5 +1,13 @@
1
1
  // HTTP via the host. `fetch` is async (host streams the body), `fetchLocal` reads a preloaded asset
2
- // synchronously. Ported from worker/src/fetch.ts over the same _creatorUtils bridge.
2
+ // synchronously. Over the `_creatorFetch` bridge (sdk/src/bridges/fetch.d.ts).
3
+ //
4
+ // Ownership (owned handles, docs/plans/hosts-unification-plan.md phase 10): a fetched body lives as long as
5
+ // the app holds the response — `_h` is the owned handle the runtime returned with the completion,
6
+ // its collection frees the host buffer. There is nothing to dispose. A LOCAL asset's buffer is the
7
+ // host's (cached by name, shared by every `fetchLocal` of that name), so its wrapper only carries
8
+ // the id. The same goes for a `File` (picker / camera / canvas export): a host buffer the wrapper
9
+ // names but does not own.
10
+
3
11
 
4
12
  export type FetchOptions = {
5
13
  body?: any
@@ -13,47 +21,50 @@ export interface FetchResponse {
13
21
  readonly status: number
14
22
  json<T = any>(): T
15
23
  text(): string
24
+ /** @deprecated A no-op: the body is freed with the response's handle (owned handles). Apps written
25
+ * against the pre-owned SDK still call it after `text()` / `json()`; remove the call. */
16
26
  dispose(): void
17
27
  }
18
28
 
19
29
  class FetchResponseImpl implements FetchResponse {
20
30
  readonly status: number
21
- private readonly _id: number
22
- constructor(id: number, status: number) {
23
- this._id = id
31
+ /** @internal The buffer handle (`_h.id` = the host buffer id). Hosts and the runtime read a buffer
32
+ * off any object carrying `_h` — a File / FetchResponse alike (FormData entries, `UIImage` src,
33
+ * `Texture.load`, `share`, `files.write`). */
34
+ readonly _h: Handle
35
+ constructor(h: Handle, status: number) {
36
+ this._h = h
24
37
  this.status = status
25
38
  }
26
39
  json<T = any>(): T {
27
- return _creatorUtils.fetchToJson(this._id)
40
+ return _creatorFetch.toJson(this._h.id)
28
41
  }
29
42
  text(): string {
30
- return _creatorUtils.fetchToText(this._id)
31
- }
32
- dispose(): void {
33
- _creatorUtils.disposeFetch(this._id)
43
+ return _creatorFetch.toText(this._h.id)
34
44
  }
45
+ /** @deprecated see the interface */
46
+ dispose(): void {}
35
47
  }
36
48
 
37
49
  export const fetch = (url: string, options: FetchOptions = {}): Promise<FetchResponse> =>
38
50
  new Promise<FetchResponse>((resolve, reject) => {
39
- _creatorUtils.fetch(url, options, (systemId, statusCode) => {
40
- resolve(new FetchResponseImpl(systemId, statusCode))
51
+ _creatorFetch.fetch(url, options, (h, statusCode) => {
52
+ resolve(new FetchResponseImpl(h, statusCode))
41
53
  }, reject)
42
54
  })
43
55
 
44
56
  export const fetchLocal = (path: string): FetchResponse =>
45
- new FetchResponseImpl(_creatorUtils.fetchLocal(path), 200)
57
+ new FetchResponseImpl({ id: _creatorFetch.local(path) }, 200) // the host's buffer: not owned
46
58
 
47
59
  /** A handle to a picked / fetched file (see openFilePicker). */
48
60
  export class File {
49
- /** @internal host buffer handle. `_`-prefixed because hosts read it off the raw object —
50
- * same key `FetchResponse` carries, so both are accepted anywhere a buffer is expected
51
- * (FormData entries, `UIImage` src, `Texture.load`, `share`). */
52
- readonly _id: number
61
+ /** @internal The host buffer (not owned — see the header). Same key `FetchResponse` carries, so
62
+ * both are accepted anywhere a buffer is expected. */
63
+ readonly _h: Handle
53
64
  readonly name: string
54
65
  readonly size: number
55
66
  constructor(systemId: number, name: string, size: number) {
56
- this._id = systemId
67
+ this._h = { id: systemId }
57
68
  this.name = name
58
69
  this.size = size
59
70
  }
@@ -62,9 +73,8 @@ export class File {
62
73
  type FormDataValue = string | number | boolean | File | FetchResponse
63
74
 
64
75
  export class FormData {
65
- // Host-facing: hosts walk `_entries` and treat any object value carrying `_id` as binary
66
- // (the buffer id), everything else as a text field. See viewer-lite utils/fetch.ts,
67
- // creator-pkg creatorUtilsHandler.cpp, iOS Fetch.swift buildMultipartBody.
76
+ // Host-facing: the runtime walks `_entries` and treats any object value carrying `_h` as binary
77
+ // (the buffer id), everything else as a text field. See runtime/native src/host/fetch.cpp.
68
78
  private _entries: [string, FormDataValue, filename?: string][] = []
69
79
 
70
80
  append(name: string, value: FormDataValue, filename?: string): void {
@@ -12,7 +12,9 @@ export function openFilePicker(
12
12
  options: OpenFilePickerOptions & { multiple?: boolean } = {},
13
13
  ): Promise<File | File[] | null> {
14
14
  return new Promise((resolve, reject) => {
15
- _creatorUtils.openFilePicker((resp) => {
15
+ // No picker on this host (headless, a hidden render): nothing picked, like a cancelled dialog.
16
+ if (typeof _creatorDevice.openFilePicker !== "function") { resolve(options.multiple ? [] : null); return }
17
+ _creatorDevice.openFilePicker((resp) => {
16
18
  if (!resp || resp.length === 0) {
17
19
  resolve(options.multiple ? [] : null)
18
20
  return
@@ -24,7 +26,7 @@ export function openFilePicker(
24
26
  }
25
27
 
26
28
  // ---- Local filesystem -------------------------------------------------------------------------
27
- // `files` is the read/write/delete half of `fetchLocal`, for platforms where the app owns a
29
+ // `files` (the `_creatorFiles` bridge, absent on sandboxed hosts) is the read/write/delete half of `fetchLocal`, for platforms where the app owns a
28
30
  // filesystem — desktop today. A RELATIVE path is resolved against the app's OWN folder (for a
29
31
  // packaged desktop app: the directory the exe sits in), never the process cwd, so a save written by
30
32
  // a shortcut and one written from a terminal land in the same place; an ABSOLUTE path goes wherever
@@ -61,8 +63,7 @@ export type FileEntry = {
61
63
  modified: number
62
64
  }
63
65
 
64
- const supported = () =>
65
- typeof _creatorUtils !== "undefined" && typeof _creatorUtils.writeLocal === "function"
66
+ const supported = () => typeof _creatorFiles !== "undefined"
66
67
 
67
68
  // The host is asked for the work only when it can do it; otherwise the promise rejects with the
68
69
  // same message the native bridge uses, so a project sees one error either way.
@@ -72,10 +73,10 @@ const run = <T>(op: string, path: string, call: (resolve: (v: T) => void, reject
72
73
  call(resolve, reject)
73
74
  })
74
75
 
75
- // A File / FetchResponse crosses as the bare host buffer handle — the same `_id` convention
76
- // FormData entries and image `src` objects use, so the bytes never travel through JS.
76
+ // A File / FetchResponse crosses as its buffer handle — the same `_h` convention FormData entries
77
+ // and image `src` objects use, so the bytes never travel through JS.
77
78
  const payload = (data: FileData) =>
78
- (typeof data === "object" && "_id" in (data as any) ? { _id: (data as any)._id } : data) as any
79
+ (typeof data === "object" && "_h" in (data as any) ? { _h: (data as any)._h } : data) as any
79
80
 
80
81
  export const files = {
81
82
  /** Is there a filesystem here (desktop)? False on web/mobile, where every other call rejects. */
@@ -86,38 +87,38 @@ export const files = {
86
87
  /** Replace the file's contents, creating the parent folders on the way. */
87
88
  write(path: string, data: FileData): Promise<void> {
88
89
  return run<void>("write", path, (resolve, reject) =>
89
- _creatorUtils.writeLocal!(path, payload(data), false, () => resolve(), reject))
90
+ _creatorFiles.write(path, payload(data), false, () => resolve(), reject))
90
91
  },
91
92
 
92
93
  /** Add to the end of the file, creating it (and the parent folders) when it isn't there yet. */
93
94
  append(path: string, data: FileData): Promise<void> {
94
95
  return run<void>("append", path, (resolve, reject) =>
95
- _creatorUtils.writeLocal!(path, payload(data), true, () => resolve(), reject))
96
+ _creatorFiles.write(path, payload(data), true, () => resolve(), reject))
96
97
  },
97
98
 
98
99
  /** The whole file as UTF-8 text — null when there is no such file. */
99
100
  readText(path: string): Promise<string | null> {
100
101
  return run<string | null>("readText", path, (resolve, reject) =>
101
- _creatorUtils.readLocalText!(path, resolve, reject))
102
+ _creatorFiles.readText(path, resolve, reject))
102
103
  },
103
104
 
104
105
  /** The whole file as bytes — null when there is no such file. */
105
106
  readBytes(path: string): Promise<Uint8Array | null> {
106
107
  return run<Uint8Array | null>("readBytes", path, (resolve, reject) =>
107
- _creatorUtils.readLocalBytes!(path, resolve, reject))
108
+ _creatorFiles.readBytes(path, resolve, reject))
108
109
  },
109
110
 
110
111
  /** Delete a file, or a directory (empty, or any directory with `{ recursive: true }`). Deleting
111
112
  * something already gone is success — what's promised is that the path is free afterwards. */
112
113
  rm(path: string, options: { recursive?: boolean } = {}): Promise<void> {
113
114
  return run<void>("rm", path, (resolve, reject) =>
114
- _creatorUtils.deleteLocal!(path, !!options.recursive, () => resolve(), reject))
115
+ _creatorFiles.remove(path, !!options.recursive, () => resolve(), reject))
115
116
  },
116
117
 
117
118
  /** Create a directory and any missing parents. An existing directory is fine. */
118
119
  mkdir(path: string): Promise<void> {
119
120
  return run<void>("mkdir", path, (resolve, reject) =>
120
- _creatorUtils.mkdirLocal!(path, () => resolve(), reject))
121
+ _creatorFiles.mkdir(path, () => resolve(), reject))
121
122
  },
122
123
 
123
124
  /** The entries of a directory, sorted by path — null when there is no such directory (an empty
@@ -126,13 +127,13 @@ export const files = {
126
127
  * is a file, not a directory, rejects. Filter in JS — `entries.filter(e => e.name.endsWith(".json"))`. */
127
128
  ls(path: string, options: { recursive?: boolean } = {}): Promise<FileEntry[] | null> {
128
129
  return run<FileEntry[] | null>("ls", path, (resolve, reject) =>
129
- _creatorUtils.listLocal!(path, !!options.recursive, resolve, reject))
130
+ _creatorFiles.list(path, !!options.recursive, resolve, reject))
130
131
  },
131
132
 
132
133
  /** What is at `path`: a file or a directory entry (`path` equal to `name`), or null when nothing
133
134
  * is there — `(await files.stat(p)) !== null` is the exists check. */
134
135
  stat(path: string): Promise<FileEntry | null> {
135
136
  return run<FileEntry | null>("stat", path, (resolve, reject) =>
136
- _creatorUtils.statLocal!(path, (e) => resolve(e ? { ...e, path: e.name } : null), reject))
137
+ _creatorFiles.stat(path, (e) => resolve(e ? { ...e, path: e.name } : null), reject))
137
138
  },
138
139
  }
@@ -7,9 +7,9 @@
7
7
  // - CONTINUOUS — mouse motion/wheel, sticks, triggers: polled every frame (`Input.mouse.delta`,
8
8
  // `Input.gamepad(0).axis('leftX')`), never events.
9
9
  // Bindings ("jump = Space | GamepadSouth") are app code: compare `e.code`, OR a few `Input.key`s.
10
- // Hosts report physical state only (`_creatorUtils.inputKey/inputRead/registerInputEvent`).
10
+ // Hosts report physical state only (`_creatorInput.key / read / registerEvent`).
11
11
 
12
- /** Continuous channel ids — the `_creatorUtils.inputRead` contract (bridges.d.ts). */
12
+ /** Continuous channel ids — the `_creatorInput.read` contract (sdk/src/bridges/input.d.ts). */
13
13
  export const InputChannel = {
14
14
  MouseX: 0, MouseY: 1, MouseDX: 2, MouseDY: 3, WheelX: 4, WheelY: 5, PointerLocked: 6,
15
15
  GamepadCount: 7, PointerOnUI: 8,
@@ -77,10 +77,12 @@ const deliver = (kind: number, code: string, gamepad: number, repeat: number) =>
77
77
  const ensureHostListener = () => {
78
78
  if (hostListenerInstalled) return
79
79
  hostListenerInstalled = true
80
- _creatorUtils.registerInputEvent?.(deliver)
80
+ input()?.registerEvent?.(deliver)
81
81
  }
82
82
 
83
- const read = (channel: number): number => _creatorUtils.inputRead?.(channel) ?? 0
83
+ // The host's input bridge, or undefined on a host without one (every read then answers "nothing").
84
+ const input = () => (typeof _creatorInput !== "undefined" ? _creatorInput : undefined)
85
+ const read = (channel: number): number => input()?.read?.(channel) ?? 0
84
86
 
85
87
  /** Mouse state — polled. Position is the cursor inside the viewport in logical px; `delta` is the
86
88
  * motion during the previous frame (raw where the OS offers it) and keeps counting while locked —
@@ -99,8 +101,8 @@ export const mouse = {
99
101
  * `if (!Input.mouse.onUI) look(Input.mouse.delta)` never turns a HUD drag into camera look.
100
102
  * Always false while `locked`. A press that fell through to the scene reads false. */
101
103
  get onUI(): boolean { return read(InputChannel.PointerOnUI) !== 0 },
102
- lock(): boolean { return _creatorUtils.inputSetPointerLock?.(true) ?? false },
103
- unlock(): void { _creatorUtils.inputSetPointerLock?.(false) },
104
+ lock(): boolean { return input()?.setPointerLock?.(true) ?? false },
105
+ unlock(): void { input()?.setPointerLock?.(false) },
104
106
  }
105
107
 
106
108
  export type GamepadAxisName = "leftX" | "leftY" | "rightX" | "rightY" | "leftTrigger" | "rightTrigger"
@@ -117,6 +119,13 @@ export interface GamepadState {
117
119
  axis(name: GamepadAxisName, deadzone?: number): number
118
120
  /** Held? Same as `Input.key(code, index)`. */
119
121
  button(code: string): boolean
122
+ /** Rumble: `strong` = the heavy low-frequency motor, `weak` = the light high-frequency one, both
123
+ * 0..1, for `durationMs` (default 200, hosts cap at 5000). A new call replaces the running
124
+ * effect. The host stops the motors by itself — after the duration, on focus loss and when the
125
+ * project is swapped — so there is nothing to clean up. False when the pad or host has no motors. */
126
+ rumble(strong: number, weak?: number, durationMs?: number): boolean
127
+ /** Stop the running rumble now. */
128
+ stopRumble(): void
120
129
  }
121
130
 
122
131
  const padCache: GamepadState[] = []
@@ -131,7 +140,11 @@ const gamepadAt = (index: number): GamepadState => {
131
140
  const v = read(base + PAD_AXIS[name])
132
141
  return deadzone > 0 && Math.abs(v) < deadzone ? 0 : v
133
142
  },
134
- button(code) { return _creatorUtils.inputKey(code, index) },
143
+ button(code) { return input()?.key?.(code, index) ?? false },
144
+ rumble(strong, weak = strong, durationMs = 200) {
145
+ return input()?.rumble?.(index, strong, weak, durationMs) ?? false
146
+ },
147
+ stopRumble() { input()?.rumble?.(index, 0, 0, 0) },
135
148
  }
136
149
  }
137
150
  return g
@@ -145,7 +158,7 @@ export const Input = {
145
158
  * frame-independent movement. `gamepad` scopes a Gamepad* code to one pad; omitted = any pad.
146
159
  */
147
160
  key(code: string, gamepad?: number): boolean {
148
- return _creatorUtils.inputKey(code, gamepad)
161
+ return input()?.key?.(code, gamepad ?? -1) ?? false
149
162
  },
150
163
 
151
164
  /** Listen for a discrete input moment. `keydown`/`keyup` cover EVERY button — keyboard, mouse,
@@ -1,13 +1,15 @@
1
- // Audio / video playback over the host. Ported from worker/src/MediaPlayer.ts, with the listener
2
- // boilerplate folded into the shared Emitter. VideoPlayer.texture (a 3D Texture) is wired in Phase 4
3
- // when the Texture type exists — see gl/.
1
+ // Audio / video playback over the host's `_creatorMedia` bridge (absent on hosts without media
2
+ // players — the players are inert there). The listener boilerplate is folded into the shared Emitter.
4
3
  //
5
- // The host no longer routes end-of-playback events back by id through a Registry: each player hands
6
- // the host a `_finished` callback at creation time. The host holds the JS closure directly — valid
7
- // on every backend (web JS, Android quickjs, iOS JavaScriptCore) — and invokes it when a play-through
8
- // completes, so there is no id table to keep in sync.
4
+ // Ownership (owned handles, docs/plans/hosts-unification-plan.md phase 10): a player is an owned resource
5
+ // — `_h` is the handle the runtime returned; the runtime counts a PLAYING player as natively owned.
6
+ // So `new AudioPlayer(src).play()` in a handler, reference dropped, plays to its end and is released
7
+ // then; a paused player you stop referencing is released at once. There is nothing to dispose. The
8
+ // end-of-playback report comes by id through ONE emitter per world; a playing player is pinned so
9
+ // the report finds it.
9
10
 
10
11
  import { Emitter } from "../core/events"
12
+ import { Pins } from "../core/pins"
11
13
  import { Texture } from "../gl/Texture"
12
14
 
13
15
  type MediaEvents = {
@@ -17,83 +19,84 @@ type MediaEvents = {
17
19
  completed: () => void
18
20
  }
19
21
 
22
+ /** The players native owns right now (playing), by id — what an end-of-playback report resolves to. */
23
+ const players = new Pins<MediaPlayer>()
24
+ /** The host's media table, or undefined on a host without one (`@optional`: the global is absent). */
25
+ const media = (): typeof _creatorMedia | undefined => (typeof _creatorMedia !== "undefined" ? _creatorMedia : undefined)
26
+ let emitterInstalled = false
27
+ const ensureEmitter = (): void => {
28
+ const m = media()
29
+ if (emitterInstalled || !m || typeof m.setEmitter !== "function") return
30
+ emitterInstalled = true
31
+ m.setEmitter((id) => { players.get(id)?._finished() })
32
+ }
33
+
20
34
  abstract class MediaPlayer extends Emitter<MediaEvents> {
21
- protected _id = -1
35
+ /** @internal The player's owned handle (`_h.id` = the host player id; -1 = no host player). */
36
+ _h: Handle = { id: -1 }
22
37
  protected _src = ""
23
38
  private _playing = false
24
39
  private _loop = false
25
40
  private _volume = 1
26
- private _disposed = false
27
-
28
- // Every host-touching operation runs through this so a disposed player can never address a freed
29
- // (and possibly reused) host slot.
30
- private _assertAlive(): void {
31
- if (this._disposed) throw new Error("MediaPlayer has been disposed")
32
- }
33
41
 
34
42
  play(): void { this.playing = true }
35
43
  pause(): void { this.playing = false }
44
+ /** Pause and rewind to the start. */
45
+ stop(): void {
46
+ this.playing = false
47
+ media()?.setTime?.(this._h.id, 0)
48
+ }
36
49
 
37
50
  get playing(): boolean { return this._playing }
38
51
  set playing(value: boolean) {
39
- this._assertAlive()
40
52
  this._playing = value
41
- _creatorUtils.updateMediaPlayerPlaying(this._id, value)
53
+ media()?.setPlaying(this._h.id, value)
54
+ if (value) players.pin(this) // playing: native owns it, the end report must find it
55
+ else players.unpin(this._h.id)
42
56
  }
43
57
 
44
58
  get volume(): number { return this._volume }
45
59
  set volume(value: number) {
46
- this._assertAlive()
47
60
  this._volume = value
48
- _creatorUtils.updateMediaPlayerVolume(this._id, value)
61
+ media()?.setVolume(this._h.id, value)
49
62
  }
50
63
 
51
64
  get loop(): boolean { return this._loop }
52
65
  set loop(value: boolean) {
53
- this._assertAlive()
54
66
  this._loop = value
55
- _creatorUtils.updateMediaPlayerLoop(this._id, value)
67
+ media()?.setLoop(this._h.id, value)
56
68
  }
57
69
 
58
70
  /** Current playback position, in seconds. Settable to seek. */
59
- get time(): number { this._assertAlive(); return _creatorUtils.getMediaPlayerTime(this._id) }
60
- set time(value: number) { this._assertAlive(); _creatorUtils.setMediaPlayerTime(this._id, value) }
71
+ get time(): number { return media()?.time?.(this._h.id) ?? 0 }
72
+ set time(value: number) { media()?.setTime?.(this._h.id, value) }
61
73
 
62
74
  /** Total media length in seconds (0 until metadata has loaded). */
63
- get duration(): number { this._assertAlive(); return _creatorUtils.getMediaPlayerDuration(this._id) }
75
+ get duration(): number { return media()?.duration?.(this._h.id) ?? 0 }
64
76
 
65
- /** True once {@link dispose} has been called; the player is unusable afterwards. */
66
- get disposed(): boolean { return this._disposed }
67
-
68
- /**
69
- * Release the underlying host media resource (decoder, network, buffers) immediately and detach
70
- * all listeners. Idempotent. Any further use of the player throws.
71
- */
72
- dispose(): void {
73
- if (this._disposed) return
74
- this._disposed = true
75
- this._playing = false
76
- this.clearListeners()
77
- _creatorUtils.removeMediaPlayer(this._id)
78
- }
79
-
80
- /** @internal — the host calls this when a play-through finishes. */
81
- protected _finished(): void {
82
- if (this._disposed) return
77
+ /** @internal — the runtime reports a play-through end. */
78
+ _finished(): void {
83
79
  if (this._loop) {
84
80
  this.dispatch("loopReached")
85
81
  } else {
86
82
  this._playing = false
83
+ players.unpin(this._h.id)
87
84
  this.dispatch("completed")
88
85
  }
89
86
  }
87
+
88
+ /** @deprecated There is nothing to dispose — a player the app stops referencing is released by
89
+ * the runtime (a playing one at its end). Kept as `stop()` so old code still silences the player;
90
+ * call `stop()` / `pause()` instead. */
91
+ dispose(): void { this.stop() }
90
92
  }
91
93
 
92
94
  export class AudioPlayer extends MediaPlayer {
93
95
  constructor(src?: string) {
94
96
  super()
95
97
  this._src = src ?? ""
96
- this._id = _creatorUtils.createAudioPlayer(this._src, () => this._finished())
98
+ ensureEmitter()
99
+ this._h = media()?.createAudio(this._src) ?? { id: -1 }
97
100
  }
98
101
  }
99
102
 
@@ -101,11 +104,12 @@ export class VideoPlayer extends MediaPlayer {
101
104
  constructor(src?: string) {
102
105
  super()
103
106
  this._src = src ?? ""
104
- this._id = _creatorUtils.createVideoPlayer(this._src, () => this._finished())
107
+ ensureEmitter()
108
+ this._h = media()?.createVideo(this._src) ?? { id: -1 }
105
109
  }
106
110
 
107
- /** A 3D Texture that samples this video (e.g. for a Material.video()). */
111
+ /** A 3D Texture that samples this video (e.g. for a Material.video()). Owned by the player. */
108
112
  get texture(): Texture {
109
- return new Texture(0, 0, _creator.createMediaPlayerTexture(this._id))
113
+ return new Texture(0, 0, _creator.createMediaPlayerTexture(this._h.id))
110
114
  }
111
115
  }
@@ -1,15 +1,19 @@
1
1
  // Small platform globals that are real (not engine-specific). Grown in Phase 1.
2
2
 
3
+ import type { ColorInput } from "../core/color"
4
+
3
5
  /** Show a transient toast notification. */
4
- export const toast = (msg: string): void => _creatorUI.toast(msg)
6
+ export const toast = (msg: string): void => _creatorTree.toast(msg, 2000)
5
7
 
6
8
  /** Open `url` in the system browser / external handler (fire-and-forget, like `share`). Silent
7
9
  * no-op on hosts without one (headless). */
8
- export const openURL = (url: string): void => _creatorUtils.openUrl?.(url)
10
+ export const openURL = (url: string): void => _creatorApp.openUrl?.(url)
9
11
 
10
12
  export type SvgSourceValue = {
11
13
  readonly svg: string
12
- tintColor: string | null
14
+ /** The source-level tint (a mask recolor under the `tintColor` style, which wins): any color, or
15
+ * a theme `var(--…)`. */
16
+ tintColor: ColorInput | null
13
17
  }
14
18
 
15
19
  /** Wrap raw SVG XML so it can be used as an image source. */
@@ -1,5 +1,5 @@
1
- // WebSocket over the host bridge. Same event surface as the browser's (open/message/close/error)
2
- // via the shared Emitter. Ported from worker/src/WebSocket.ts.
1
+ // WebSocket over the host's `_creatorSocket` bridge. Same event surface as the browser's
2
+ // (open/message/close/error) via the shared Emitter.
3
3
 
4
4
  import { Emitter } from "../core/events"
5
5
 
@@ -13,18 +13,19 @@ type SocketEvents = {
13
13
  export class WebSocket extends Emitter<SocketEvents> {
14
14
  private _id: number | null = null
15
15
 
16
- constructor(url: string, headers?: Record<string, string>) {
16
+ constructor(url: string) {
17
17
  super()
18
- this._id = _creatorUtils.websocketOpen(url, (channel, data) => this._onMessage(channel, data), headers)
18
+ this._id = _creatorSocket.open(url, (channel, data) => this._onMessage(channel, data))
19
19
  }
20
20
 
21
- send(message: string | ArrayBuffer): void {
22
- if (this._id !== null) _creatorUtils.websocketSend(this._id, message)
21
+ /** Send a text frame. */
22
+ send(message: string): void {
23
+ if (this._id !== null) _creatorSocket.send(this._id, message)
23
24
  }
24
25
 
25
26
  close(): void {
26
27
  if (this._id !== null) {
27
- _creatorUtils.websocketClose(this._id)
28
+ _creatorSocket.close(this._id)
28
29
  this._id = null
29
30
  }
30
31
  }
@@ -68,7 +68,6 @@ export const __rpc = (serverUrl: string, id: string) => {
68
68
  }
69
69
  let body: any
70
70
  try { body = res.json() } catch { body = null }
71
- res.dispose()
72
71
  if (body && typeof body === "object" && "session" in body) setSession(serverUrl, body.session ?? null)
73
72
  if (body && body.ok === true) return body.result
74
73
  if (body && body.ok === false) throw new RpcError(body.status ?? res.status, body.message ?? "")
@@ -132,8 +131,7 @@ class ChannelSocket {
132
131
  if (this.ws || this.timer !== null) return
133
132
  this.closedByUs = false
134
133
  const url = this.serverUrl.replace(/^http/, "ws") + "/ws"
135
- const session = getSession(this.serverUrl)
136
- const ws = new WebSocket(url, session ? { authorization: `Bearer ${session}` } : undefined)
134
+ const ws = new WebSocket(url)
137
135
  this.ws = ws
138
136
  ws.addEventListener("open", () => {
139
137
  this.open = true
@@ -1,7 +1,7 @@
1
1
  // A host-registered headless service (geolocation, bluetooth, contacts, …) — the UI-less sibling
2
2
  // of NativeView (docs/service-channel-plan.md). The host registers a *capability*
3
3
  // (`registerService("geolocation") { params, channel -> ... }`); the SDK talks to it over the
4
- // shared JSON call/event protocol (runtime/channel.ts) via `_creatorUtils.service*`.
4
+ // shared call / event protocol (runtime/channel.ts) via `_creatorService`.
5
5
  //
6
6
  // Not a public global itself: apps meet services through typed plugin wrappers (Geolocation —
7
7
  // src/plugins/) or the raw `Service(name)` global (src/plugins/service.ts), both thin layers
@@ -26,18 +26,22 @@ export class ServiceClient {
26
26
 
27
27
  /** Whether this host registered a `registerService` factory under `name`. */
28
28
  static isSupported(name: string): boolean {
29
- return typeof _creatorUtils !== "undefined" && typeof _creatorUtils.isServiceSupported === "function"
30
- && _creatorUtils.isServiceSupported(name)
29
+ return typeof _creatorService !== "undefined" && _creatorService.supported(name)
30
+ }
31
+
32
+ /** The contract version of the host's service `name`: 0 when it has none, 1 for one written by hand. */
33
+ static version(name: string): number {
34
+ return typeof _creatorService !== "undefined" ? _creatorService.version(name) : 0
31
35
  }
32
36
 
33
37
  /** @internal open (or reuse) the host session; throws when the host has no channel/factory. */
34
38
  private _ensureOpen(): number {
35
39
  if (this._serviceId !== null) return this._serviceId
36
- if (typeof _creatorUtils === "undefined" || typeof _creatorUtils.serviceOpen !== "function") {
37
- throw new Error(`Service("${this.serviceName}") is not supported on this host (requires _creatorUtils.serviceOpen)`)
40
+ if (typeof _creatorService === "undefined") {
41
+ throw new Error(`Service("${this.serviceName}") is not supported on this host (no service channel)`)
38
42
  }
39
- const id = _creatorUtils.serviceOpen(this.serviceName, JSON.stringify(this.params ?? {}),
40
- (event: string, dataJson?: string) => _channelEmit(this._listeners, event, dataJson))
43
+ const id = _creatorService.open(this.serviceName, JSON.stringify(this.params ?? {}),
44
+ (event: string, data?: unknown) => _channelEmit(this._listeners, event, data))
41
45
  if (id < 0) {
42
46
  throw new Error(`Service("${this.serviceName}") is not registered on this host`)
43
47
  }
@@ -46,8 +50,11 @@ export class ServiceClient {
46
50
  }
47
51
 
48
52
  /** Invoke a method on the service (`geo.call("getCurrent", options)`). Args and the result are
49
- * JSON-serialized. Rejects if the host has no such service/method. */
50
- call(method: string, ...args: any[]): Promise<any> {
53
+ * plain data (and Uint8Array). Rejects if the host has no such service/method. */
54
+ call(method: string, ...args: any[]): Promise<any> { return this._channelSend(method, args) }
55
+
56
+ /** @internal One call over the channel (runtime/channel.ts). */
57
+ _channelSend(method: string, args: any[]): Promise<any> {
51
58
  let id: number
52
59
  try {
53
60
  id = this._ensureOpen()
@@ -55,8 +62,8 @@ export class ServiceClient {
55
62
  return Promise.reject(e)
56
63
  }
57
64
  return _channelCall(
58
- typeof _creatorUtils !== "undefined" ? _creatorUtils : undefined, "serviceCall",
59
- `Service("${this.serviceName}") is not supported on this host (requires _creatorUtils.serviceCall)`,
65
+ typeof _creatorService !== "undefined" ? _creatorService : undefined, "call",
66
+ `Service("${this.serviceName}") is not supported on this host (no service channel)`,
60
67
  id, method, args)
61
68
  }
62
69
 
@@ -76,8 +83,6 @@ export class ServiceClient {
76
83
  if (this._serviceId === null) return
77
84
  const id = this._serviceId
78
85
  this._serviceId = null
79
- if (typeof _creatorUtils !== "undefined" && typeof _creatorUtils.serviceClose === "function") {
80
- _creatorUtils.serviceClose(id)
81
- }
86
+ if (typeof _creatorService !== "undefined") _creatorService.close(id)
82
87
  }
83
88
  }
@@ -1,9 +1,10 @@
1
- // Share a media File (image/video) via the OS share sheet. Wraps `_creatorUtils.shareMedia`, which
1
+ // Share a media File (image/video) via the OS share sheet. Wraps `_creatorDevice.share`, which
2
2
  // takes the host buffer id behind the File. On iOS the sheet also lets the user save to Files/Photos;
3
- // on web it saves (downloads) the file. `text` is an optional caption/message.
3
+ // on web it saves (downloads) the file. `text` is an optional caption/message. A silent no-op on
4
+ // hosts without a share slot (headless).
4
5
 
5
6
  import type { File, FetchResponse } from "./fetch"
6
7
 
7
8
  export function share(media: File | FetchResponse, text?: string): void {
8
- _creatorUtils.shareMedia((media as any)._id, text)
9
+ _creatorDevice.share?.((media as any)._h.id, text)
9
10
  }
@@ -1,13 +1,15 @@
1
- // Key/value persistence backed by the host. Same shape as the web localStorage subset projects use.
1
+ // Key/value persistence backed by the host (`_creatorStorage`). Same shape as the web localStorage
2
+ // subset projects use.
3
+
2
4
 
3
5
  export const localStorage = {
4
6
  getItem(key: string): string | null {
5
- return _creatorUtils.localStorageGetValue(key)
7
+ return _creatorStorage.get(key)
6
8
  },
7
9
  setItem(key: string, value: string): void {
8
- _creatorUtils.localStorageSetValue(key, value)
10
+ _creatorStorage.set(key, value)
9
11
  },
10
12
  removeItem(key: string): void {
11
- _creatorUtils.localStorageRemoveValue(key)
13
+ _creatorStorage.remove(key)
12
14
  },
13
15
  }