UNPKG

@javelin/ecs

Version:

367 lines 12.1 kB
import { $kind, assert, FieldKind, isField, isSchema, isSimple, mutableEmpty, } from "@javelin/core"; import { getSchemaId } from "./component"; import { UNSAFE_internals } from "./internal"; export const $self = Symbol("javelin_proxy_self"); export const $touched = Symbol("javelin_proxy_touched"); export const $changes = Symbol("javelin_proxy_changes"); export const $delete = Symbol("javelin_proxy_deleted"); const proxies = new WeakMap(); const simpleStructHandler = { get(target, key) { if (key === $self) return target; target[$touched] = true; return target[key]; }, set(target, key, value) { const changes = target[$changes]; target[key] = value; target[$touched] = true; changes.changes[key] = value; changes.dirty = true; return true; }, }; const structHandler = { get(target, key) { if (key === $self) return target; const value = target[key]; target[$touched] = true; if (typeof value === "object" && value !== null) { return proxify(value, target, key); } return value; }, set: simpleStructHandler.set, }; const simpleArrayHandler = simpleStructHandler; const arrayHandler = { get: structHandler.get, set: simpleArrayHandler.set, }; const simpleObjectHandler = { ...simpleStructHandler, deleteProperty(target, key) { const changes = target[$changes]; delete target[key]; target[$touched] = true; changes.changes[key] = $delete; changes.dirty = true; return true; }, }; const objectHandler = { ...structHandler, deleteProperty: simpleObjectHandler.deleteProperty, }; const setHandler = { get(target, key) { if (key === $self) return target; const value = target[key]; target[$touched] = true; return typeof value === "function" ? new Proxy(value, setMethodHandler) : value; }, }; const setMethodHandler = { apply(method, target, args) { const { [$self]: self, [$changes]: changes } = target; target[$touched] = true; switch (method) { case Set.prototype.add: changes.changes.add.push(args[0]); changes.dirty = true; break; case Set.prototype.delete: changes.changes.delete.push(args[0]); changes.dirty = true; break; case Set.prototype.clear: self.forEach(value => changes.changes.delete.push(value)); changes.dirty = true; break; } return method.apply(self, args); }, }; const mapHandler = { get(target, key, receiver) { if (key === $self) return target; const value = Reflect.get(target, key, receiver); target[$touched] = true; return typeof value === "function" ? new Proxy(value, mapMethodHandler) : value; }, }; const mapMethodHandler = { apply(method, target, args) { const { [$self]: self } = target; const { [$changes]: changes } = self; self[$touched] = true; switch (method) { case Map.prototype.get: { const value = method.apply(self, args); if (typeof value === "object" && value !== null) { return proxify(value, self, args[0]); } } case Map.prototype.set: changes.changes.set(args[0], args[1]); changes.dirty = true; break; case Map.prototype.delete: changes.changes.set(args[0], $delete); changes.dirty = true; break; case Map.prototype.clear: self.forEach((_, key) => changes.changes.set(key, $delete)); changes.dirty = true; return self.clear(); } return method.apply(self, args); }, }; function getHandler(node) { const simple = isSimple(node); if (isField(node)) { switch (node[$kind]) { case FieldKind.Array: return simple ? simpleArrayHandler : arrayHandler; case FieldKind.Object: return simple ? simpleObjectHandler : objectHandler; case FieldKind.Set: return setHandler; case FieldKind.Map: return mapHandler; default: throw new Error("Failed to observe object: cannot observe a primitive type"); } } return simple ? simpleStructHandler : structHandler; } function getChanges(node) { const base = { dirty: false, node }; if (isField(node)) { switch (node[$kind]) { case FieldKind.Array: return { ...base, changes: {} }; case FieldKind.Object: return { ...base, changes: {} }; case FieldKind.Set: return { ...base, changes: { add: [], delete: [] } }; case FieldKind.Map: return { ...base, changes: new Map() }; } } return { ...base, changes: {} }; } const descriptorBase = { configurable: false, enumerable: true, writable: false, }; function register(object, node) { const changes = getChanges(node); const observed = Object.defineProperties(object, { [$self]: { ...descriptorBase, value: object }, [$changes]: { ...descriptorBase, value: changes }, }); const handler = getHandler(node); const proxy = new Proxy(observed, handler); proxies.set(object, proxy); return proxy; } function proxify(object, parent, key) { var _a; const parentNode = parent[$changes].node; let node; if (isSchema(parentNode)) { node = parentNode.fieldsByKey[key]; } else { assert("element" in parentNode); node = parentNode.element; } return (_a = proxies.get(object)) !== null && _a !== void 0 ? _a : register(object, node); } export function observe(component) { var _a; ; component[$touched] = true; return ((_a = proxies.get(component)) !== null && _a !== void 0 ? _a : register(component, UNSAFE_internals.model[getSchemaId(component)])); } function clearObservedChangesInner(object, node) { if (object[$touched] !== true) { return; } const changes = object[$changes]; if (isSchema(node)) { for (const prop in changes.changes) { delete changes.changes[prop]; } for (let i = 0; i < node.fields.length; i++) { clearObservedChangesInner(object[node.keys[i]], node.fields[i]); } } else if ("element" in node) { const element = node.element; switch (node[$kind]) { case FieldKind.Array: { for (const prop in changes.changes) { delete changes.changes[prop]; } for (let i = 0; i < object.length; i++) { clearObservedChangesInner(object[i], element); } break; } case FieldKind.Object: { for (const prop in changes.changes) { delete changes.changes[prop]; } for (const prop in object) { clearObservedChangesInner(object[prop], element); } break; } case FieldKind.Set: { mutableEmpty(changes.changes.add); mutableEmpty(changes.changes.delete); break; } case FieldKind.Map: { ; changes.changes.clear(); object.forEach(value => clearObservedChangesInner(value, element)); break; } } } changes.dirty = false; object[$touched] = false; } export function clearObservedChanges(component) { const self = $self in component ? component[$self] : component; const node = UNSAFE_internals.model[getSchemaId(self)]; return clearObservedChangesInner(self, node); } export function getFieldValue(node, object, fieldId, traverse) { let t = 0; let key = null; let ref = object; outer: while (node.id !== fieldId) { if (isField(node)) { assert("element" in node); key = traverse[t++]; switch (node[$kind]) { case FieldKind.Array: case FieldKind.Object: ref = ref[key]; break; case FieldKind.Map: ref = ref.get(key); break; default: throw new Error("Failed to apply change: invalid target field"); } node = node.element; } else { for (let i = 0; i < node.fields.length; i++) { const child = node.fields[i]; if (child.lo <= fieldId && child.hi >= fieldId) { key = node.keys[i]; node = child; if (node.id !== fieldId) { ref = ref[key]; } continue outer; } } } } return ref; } function createPatchInner(object, patch = { changes: new Map(), children: new Map() }) { const self = object[$self]; const { [$changes]: { node, changes }, } = self; const simple = isSimple(node); if (isSchema(node)) { if (simple) { for (let i = 0; i < node.fields.length; i++) { const key = node.keys[i]; if (key in changes) patch.changes.set(key, self[key]); } } else { for (let i = 0; i < node.fields.length; i++) { const key = node.keys[i]; const value = self[key]; if (key in changes) patch.changes.set(key, value); if (value[$touched]) { patch.children.set(key, createPatchInner(value, patch.children.get(key))); } } } } else if ("element" in node) { switch (node[$kind]) { case FieldKind.Array: if (simple) { for (let i = 0; i < self.length; i++) { if (i in changes) patch.changes.set(i, self[i]); } } else { for (let i = 0; i < self.length; i++) { const value = self[i]; if (i in changes) patch.changes.set(i, value); if (value[$touched]) patch.children.set(i, createPatchInner(value, patch.children.get(i))); } } break; case FieldKind.Map: if (simple) { ; changes.forEach((value, key) => patch.changes.set(key, value)); } else { ; self.forEach((value, key) => { if (changes.has(key)) { patch.changes.set(key, value); if (value[$touched]) patch.children.set(key, createPatchInner(value, patch.children.get(key))); } }); } break; } } return patch; } export function createPatch(component, patch = { schemaId: getSchemaId(component), children: new Map(), changes: new Map(), }) { if ($changes in component) { createPatchInner(component, patch); } return patch; } export function resetPatch(patch) { patch.changes.clear(); patch.children.clear(); } //# sourceMappingURL=observe.js.map