UNPKG

reactronic

Version:

Reactronic - Transactional Reactive State Management

252 lines (251 loc) 11.3 kB
import { UNDEF } from "../util/Utils.js"; import { Log, misuse } from "../util/Dbg.js"; import { Kind, Reentrance, Isolation } from "../Enums.js"; import { ObjectVersion, ObjectHandle, ContentFootprint, Meta } from "./Data.js"; import { Changeset, Dump, EMPTY_OBJECT_VERSION } from "./Changeset.js"; export class MvccObject { constructor(isSignal) { const proto = new.target.prototype; const initial = Meta.getFrom(proto, Meta.Initial); const h = Mvcc.createHandleForMvccObject(proto, this, initial, new.target.name, isSignal); return h.proxy; } [Symbol.toStringTag]() { const h = Meta.get(this, Meta.Handle); return Dump.obj(h); } } export class TxObject extends MvccObject { constructor() { super(false); } } export class SxObject extends MvccObject { constructor() { super(true); } } const DEFAULT_OPTIONS = Object.freeze({ kind: Kind.plain, isolation: Isolation.joinToCurrentTransaction, order: 0, noSideEffects: false, signalArgs: false, throttling: Number.MAX_SAFE_INTEGER, reentrance: Reentrance.preventWithError, allowObsoleteToFinish: false, journal: undefined, indicator: null, logging: undefined, }); export class OptionsImpl { constructor(getter, setter, existing, patch, implicit) { this.getter = getter !== undefined ? getter : existing.getter; this.setter = setter !== undefined ? setter : existing.setter; this.kind = merge(DEFAULT_OPTIONS.kind, existing.kind, patch.kind, implicit); this.isolation = merge(DEFAULT_OPTIONS.isolation, existing.isolation, patch.isolation, implicit); this.order = merge(DEFAULT_OPTIONS.order, existing.order, patch.order, implicit); this.noSideEffects = merge(DEFAULT_OPTIONS.noSideEffects, existing.noSideEffects, patch.noSideEffects, implicit); this.signalArgs = merge(DEFAULT_OPTIONS.signalArgs, existing.signalArgs, patch.signalArgs, implicit); this.throttling = merge(DEFAULT_OPTIONS.throttling, existing.throttling, patch.throttling, implicit); this.reentrance = merge(DEFAULT_OPTIONS.reentrance, existing.reentrance, patch.reentrance, implicit); this.allowObsoleteToFinish = merge(DEFAULT_OPTIONS.allowObsoleteToFinish, existing.allowObsoleteToFinish, patch.allowObsoleteToFinish, implicit); this.journal = merge(DEFAULT_OPTIONS.journal, existing.journal, patch.journal, implicit); this.indicator = merge(DEFAULT_OPTIONS.indicator, existing.indicator, patch.indicator, implicit); this.logging = merge(DEFAULT_OPTIONS.logging, existing.logging, patch.logging, implicit); if (Log.isOn) Object.freeze(this); } } OptionsImpl.INITIAL = Object.freeze(new OptionsImpl(UNDEF, UNDEF, Object.assign({ getter: UNDEF, setter: UNDEF }, DEFAULT_OPTIONS), {}, false)); function merge(def, existing, patch, implicit) { return patch !== undefined && (existing === def || !implicit) ? patch : existing; } export class Mvcc { constructor(isSignal) { this.isSignal = isSignal; } getPrototypeOf(h) { return Reflect.getPrototypeOf(h.data); } get(h, fk, receiver) { let result; if (fk !== Meta.Handle) { const cs = Changeset.current(); const ov = cs.getObjectVersion(h, fk); result = ov.data[fk]; if (result instanceof ContentFootprint && !result.isComputed) { if (this.isSignal) Changeset.markUsed(result, ov, fk, h, Kind.plain, false); result = result.content; } else result = Reflect.get(h.data, fk, receiver); } else result = h; return result; } set(h, fk, value, receiver) { const cs = Changeset.edit(); const ov = cs.getEditableObjectVersion(h, fk, value); if (ov !== EMPTY_OBJECT_VERSION) cs.setFieldContent(h, fk, ov, value, receiver, Mvcc.sensitivity); else h.data[fk] = value; return true; } has(h, fk) { const ov = Changeset.current().getObjectVersion(h, fk); return fk in ov.data || fk in h.data; } defineProperty(h, name, attributes) { Object.defineProperty(h.data, name, attributes); return true; } getOwnPropertyDescriptor(h, fk) { const ov = Changeset.current().getObjectVersion(h, fk); const pd = Reflect.getOwnPropertyDescriptor(ov.data, fk); if (pd) pd.configurable = pd.writable = true; return pd; } ownKeys(h) { const ov = Changeset.current().getObjectVersion(h, Meta.Handle); const result = []; for (const fk of Object.getOwnPropertyNames(ov.data)) { const field = ov.data[fk]; if (!(field instanceof ContentFootprint) || !field.isComputed) result.push(fk); } return result; } static decorateData(isSignal, proto, fk) { if (isSignal) { Meta.acquire(proto, Meta.Initial)[fk] = new ContentFootprint(undefined, 0); const get = function () { const h = Mvcc.acquireHandle(this); return Mvcc.sx.get(h, fk, this); }; const set = function (value) { const h = Mvcc.acquireHandle(this); return Mvcc.sx.set(h, fk, value, this); }; const enumerable = true; const configurable = false; return Object.defineProperty(proto, fk, { get, set, enumerable, configurable }); } else Meta.acquire(proto, Meta.Initial)[fk] = Meta.Raw; } static decorateOperation(implicit, decorator, options, proto, member, pd) { var _a, _b, _c, _d; if (pd === undefined || pd === proto) pd = EMPTY_PROP_DESCRIPTOR; const enumerable = (_a = pd.enumerable) !== null && _a !== void 0 ? _a : true; const configurable = (_b = pd.configurable) !== null && _b !== void 0 ? _b : true; const opts = Mvcc.rememberOperationOptions(proto, member, (_c = pd.value) !== null && _c !== void 0 ? _c : pd.get, (_d = pd.value) !== null && _d !== void 0 ? _d : pd.set, true, configurable, options, implicit); if (opts.getter === opts.setter) { const bootstrap = function () { const h = Mvcc.acquireHandle(this); const operation = Mvcc.createOperationDescriptor(h, member, opts); Object.defineProperty(h.data, member, { value: operation, enumerable, configurable }); return operation; }; return Object.defineProperty(proto, member, { get: bootstrap, enumerable, configurable: true }); } else if (opts.setter === UNDEF) { const bootstrap = function () { const h = Mvcc.acquireHandle(this); const operation = Mvcc.createOperationDescriptor(h, member, opts); Object.defineProperty(h.data, member, { get: operation, enumerable, configurable }); return operation.call(this); }; return Object.defineProperty(proto, member, { get: bootstrap, enumerable, configurable: true }); } else throw misuse(`${proto.constructor.name}.${member.toString()} has setter and cannot be decorated with @${decorator.name}`); } static decorateOperationParametrized(decorator, options) { return function (proto, prop, pd) { return Mvcc.decorateOperation(false, decorator, options, proto, prop, pd); }; } static acquireHandle(obj) { let h = obj[Meta.Handle]; if (!h) { if (obj !== Object(obj) || Array.isArray(obj)) throw misuse("only objects can be signalling"); const initial = Meta.getFrom(Object.getPrototypeOf(obj), Meta.Initial); const ov = new ObjectVersion(EMPTY_OBJECT_VERSION.changeset, EMPTY_OBJECT_VERSION, Object.assign({}, initial)); h = new ObjectHandle(obj, obj, Mvcc.sx, ov, obj.constructor.name); Meta.set(ov.data, Meta.Handle, h); Meta.set(obj, Meta.Handle, h); Meta.set(ov.data, Meta.Revision, new ContentFootprint(1, 0)); } return h; } static createHandleForMvccObject(proto, data, blank, hint, isSignal) { const ctx = Changeset.edit(); const mvcc = isSignal ? Mvcc.sx : Mvcc.tx; const h = new ObjectHandle(data, undefined, mvcc, EMPTY_OBJECT_VERSION, hint); ctx.getEditableObjectVersion(h, Meta.Handle, blank); if (!Mvcc.reactivityAutoStartDisabled) for (const fk in Meta.getFrom(proto, Meta.Reactive)) h.proxy[fk][Meta.Descriptor].markObsolete(); return h; } static setProfilingMode(isOn, options) { if (isOn) { Mvcc.repetitiveUsageWarningThreshold = options && options.repetitiveUsageWarningThreshold !== undefined ? options.repetitiveUsageWarningThreshold : 10; Mvcc.mainThreadBlockingWarningThreshold = options && options.mainThreadBlockingWarningThreshold !== undefined ? options.mainThreadBlockingWarningThreshold : 14; Mvcc.asyncActionDurationWarningThreshold = options && options.asyncActionDurationWarningThreshold !== undefined ? options.asyncActionDurationWarningThreshold : 300; Changeset.garbageCollectionSummaryInterval = options && options.garbageCollectionSummaryInterval !== undefined ? options.garbageCollectionSummaryInterval : 100; } else { Mvcc.repetitiveUsageWarningThreshold = Number.MAX_SAFE_INTEGER; Mvcc.mainThreadBlockingWarningThreshold = Number.MAX_SAFE_INTEGER; Mvcc.asyncActionDurationWarningThreshold = Number.MAX_SAFE_INTEGER; Changeset.garbageCollectionSummaryInterval = Number.MAX_SAFE_INTEGER; } } static sensitive(sensitivity, func, ...args) { const restore = Mvcc.sensitivity; Mvcc.sensitivity = sensitivity; try { return func(...args); } finally { Mvcc.sensitivity = restore; } } static setHint(obj, hint) { if (hint) { const h = Mvcc.acquireHandle(obj); h.hint = hint; } return obj; } static getHint(obj) { const h = Mvcc.acquireHandle(obj); return h.hint; } } Mvcc.reactivityAutoStartDisabled = false; Mvcc.repetitiveUsageWarningThreshold = Number.MAX_SAFE_INTEGER; Mvcc.mainThreadBlockingWarningThreshold = Number.MAX_SAFE_INTEGER; Mvcc.asyncActionDurationWarningThreshold = Number.MAX_SAFE_INTEGER; Mvcc.sensitivity = false; Mvcc.tx = new Mvcc(false); Mvcc.sx = new Mvcc(true); Mvcc.createOperationDescriptor = function (h, fk, options) { throw misuse("this implementation of createOperationDescriptor should never be called"); }; Mvcc.rememberOperationOptions = function (proto, fk, getter, setter, enumerable, configurable, options, implicit) { throw misuse("this implementation of rememberOperationOptions should never be called"); }; const EMPTY_PROP_DESCRIPTOR = { configurable: true, enumerable: true, value: undefined, };