reactronic
Version:
Reactronic - Transactional Reactive State Management
252 lines (251 loc) • 11.3 kB
JavaScript
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,
};