UNPKG

@javelin/ecs

Version:

375 lines 13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resetPatch = exports.createPatch = exports.getFieldValue = exports.clearObservedChanges = exports.observe = exports.$delete = exports.$changes = exports.$touched = exports.$self = void 0; const core_1 = require("@javelin/core"); const component_1 = require("./component"); const internal_1 = require("./internal"); exports.$self = Symbol("javelin_proxy_self"); exports.$touched = Symbol("javelin_proxy_touched"); exports.$changes = Symbol("javelin_proxy_changes"); exports.$delete = Symbol("javelin_proxy_deleted"); const proxies = new WeakMap(); const simpleStructHandler = { get(target, key) { if (key === exports.$self) return target; target[exports.$touched] = true; return target[key]; }, set(target, key, value) { const changes = target[exports.$changes]; target[key] = value; target[exports.$touched] = true; changes.changes[key] = value; changes.dirty = true; return true; }, }; const structHandler = { get(target, key) { if (key === exports.$self) return target; const value = target[key]; target[exports.$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[exports.$changes]; delete target[key]; target[exports.$touched] = true; changes.changes[key] = exports.$delete; changes.dirty = true; return true; }, }; const objectHandler = { ...structHandler, deleteProperty: simpleObjectHandler.deleteProperty, }; const setHandler = { get(target, key) { if (key === exports.$self) return target; const value = target[key]; target[exports.$touched] = true; return typeof value === "function" ? new Proxy(value, setMethodHandler) : value; }, }; const setMethodHandler = { apply(method, target, args) { const { [exports.$self]: self, [exports.$changes]: changes } = target; target[exports.$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 === exports.$self) return target; const value = Reflect.get(target, key, receiver); target[exports.$touched] = true; return typeof value === "function" ? new Proxy(value, mapMethodHandler) : value; }, }; const mapMethodHandler = { apply(method, target, args) { const { [exports.$self]: self } = target; const { [exports.$changes]: changes } = self; self[exports.$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], exports.$delete); changes.dirty = true; break; case Map.prototype.clear: self.forEach((_, key) => changes.changes.set(key, exports.$delete)); changes.dirty = true; return self.clear(); } return method.apply(self, args); }, }; function getHandler(node) { const simple = core_1.isSimple(node); if (core_1.isField(node)) { switch (node[core_1.$kind]) { case core_1.FieldKind.Array: return simple ? simpleArrayHandler : arrayHandler; case core_1.FieldKind.Object: return simple ? simpleObjectHandler : objectHandler; case core_1.FieldKind.Set: return setHandler; case core_1.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 (core_1.isField(node)) { switch (node[core_1.$kind]) { case core_1.FieldKind.Array: return { ...base, changes: {} }; case core_1.FieldKind.Object: return { ...base, changes: {} }; case core_1.FieldKind.Set: return { ...base, changes: { add: [], delete: [] } }; case core_1.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, { [exports.$self]: { ...descriptorBase, value: object }, [exports.$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[exports.$changes].node; let node; if (core_1.isSchema(parentNode)) { node = parentNode.fieldsByKey[key]; } else { core_1.assert("element" in parentNode); node = parentNode.element; } return (_a = proxies.get(object)) !== null && _a !== void 0 ? _a : register(object, node); } function observe(component) { var _a; ; component[exports.$touched] = true; return ((_a = proxies.get(component)) !== null && _a !== void 0 ? _a : register(component, internal_1.UNSAFE_internals.model[component_1.getSchemaId(component)])); } exports.observe = observe; function clearObservedChangesInner(object, node) { if (object[exports.$touched] !== true) { return; } const changes = object[exports.$changes]; if (core_1.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[core_1.$kind]) { case core_1.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 core_1.FieldKind.Object: { for (const prop in changes.changes) { delete changes.changes[prop]; } for (const prop in object) { clearObservedChangesInner(object[prop], element); } break; } case core_1.FieldKind.Set: { core_1.mutableEmpty(changes.changes.add); core_1.mutableEmpty(changes.changes.delete); break; } case core_1.FieldKind.Map: { ; changes.changes.clear(); object.forEach(value => clearObservedChangesInner(value, element)); break; } } } changes.dirty = false; object[exports.$touched] = false; } function clearObservedChanges(component) { const self = exports.$self in component ? component[exports.$self] : component; const node = internal_1.UNSAFE_internals.model[component_1.getSchemaId(self)]; return clearObservedChangesInner(self, node); } exports.clearObservedChanges = clearObservedChanges; function getFieldValue(node, object, fieldId, traverse) { let t = 0; let key = null; let ref = object; outer: while (node.id !== fieldId) { if (core_1.isField(node)) { core_1.assert("element" in node); key = traverse[t++]; switch (node[core_1.$kind]) { case core_1.FieldKind.Array: case core_1.FieldKind.Object: ref = ref[key]; break; case core_1.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; } exports.getFieldValue = getFieldValue; function createPatchInner(object, patch = { changes: new Map(), children: new Map() }) { const self = object[exports.$self]; const { [exports.$changes]: { node, changes }, } = self; const simple = core_1.isSimple(node); if (core_1.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[exports.$touched]) { patch.children.set(key, createPatchInner(value, patch.children.get(key))); } } } } else if ("element" in node) { switch (node[core_1.$kind]) { case core_1.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[exports.$touched]) patch.children.set(i, createPatchInner(value, patch.children.get(i))); } } break; case core_1.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[exports.$touched]) patch.children.set(key, createPatchInner(value, patch.children.get(key))); } }); } break; } } return patch; } function createPatch(component, patch = { schemaId: component_1.getSchemaId(component), children: new Map(), changes: new Map(), }) { if (exports.$changes in component) { createPatchInner(component, patch); } return patch; } exports.createPatch = createPatch; function resetPatch(patch) { patch.changes.clear(); patch.children.clear(); } exports.resetPatch = resetPatch; //# sourceMappingURL=observe.js.map