@empirica/core
Version:
Empirica Core
1,241 lines (1,230 loc) • 33.5 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/player/classic/index.ts
var classic_exports = {};
__export(classic_exports, {
Attribute: () => Attribute,
Attributes: () => Attributes,
EmpiricaClassic: () => EmpiricaClassic,
Game: () => Game,
Globals: () => Globals,
Player: () => Player,
PlayerGame: () => PlayerGame,
PlayerRound: () => PlayerRound,
PlayerStage: () => PlayerStage,
Round: () => Round,
Scope: () => Scope2,
Scopes: () => Scopes2,
Stage: () => Stage,
Step: () => Step,
Steps: () => Steps,
TajribaProvider: () => TajribaProvider,
createNewParticipant: () => createNewParticipant,
isDevelopment: () => isDevelopment,
isProduction: () => isProduction,
isTest: () => isTest
});
module.exports = __toCommonJS(classic_exports);
// src/shared/attributes.ts
var import_rxjs = require("rxjs");
// src/utils/console.ts
var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
var logsMock;
var colorHex = {
[1 /* Bold */]: "font-weight: bold",
[30 /* Black */]: "color: #000000",
[31 /* Red */]: "color: #cc0000",
[32 /* Green */]: "color: #4e9a06",
[33 /* Yellow */]: "color: #c4a000",
[34 /* Blue */]: "color: #729fcf",
[35 /* Magenta */]: "color: #75507b",
[36 /* Cyan */]: "color: #06989a",
[37 /* White */]: "color: #d3d7cf",
[90 /* DarkGray */]: "color: #555753"
};
var levels = {
trace: 0,
debug: 1,
log: 2,
info: 2,
warn: 3,
error: 4
};
var reversLevels = {};
for (const key in levels) {
reversLevels[levels[key]] = key;
}
var currentLevel = 2;
function formatConsoleDate(date, level) {
var hour = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
var milliseconds = date.getMilliseconds();
const str = (hour < 10 ? "0" + hour : hour) + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + ("00" + milliseconds).slice(-3);
if (isBrowser) {
const ts = colorize(str, 90 /* DarkGray */).concat(level);
return [ts[0] + " " + level[0], ts[1], level[1]];
}
return colorize(str, 90 /* DarkGray */).concat(level);
}
var createLogger = (lvl, level) => {
return (...args) => {
if (lvl < currentLevel) {
return;
}
if (logsMock) {
logsMock.log({ level: reversLevels[lvl], args });
return;
}
if (args.length === 1) {
switch (typeof args[0]) {
case "string":
for (const line of args[0].split("\n")) {
console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(line));
}
return;
case "object":
if (args[0] instanceof Error) {
const error2 = args[0];
const prettyErr = error2.name + ": " + error2.message.replace(new RegExp(`^${error2.name}[: ]*`), "") + "\n" + (error2.stack || "").split("\n").map((line) => line.trim()).map((line) => {
if (line.startsWith(error2.name + ": " + error2.message))
return null;
if (line.startsWith("at")) {
return " " + line;
}
return line;
}).filter(Boolean).join("\n");
for (const line of prettyErr.split("\n")) {
console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(line));
}
return;
}
}
}
console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(args));
};
};
function colorize(s, ...cc) {
if (isBrowser) {
const attr = [];
for (const c of cc) {
attr.push(colorHex[c]);
}
return [`%c${s}`, attr.join("; ")];
}
let out = "";
for (const c of cc) {
out += `\x1B[${c}m`;
}
out += `${s}\x1B[0m`;
return [out];
}
var trace = createLogger(0, colorize("TRC", 35 /* Magenta */));
var debug = createLogger(1, colorize("DBG", 33 /* Yellow */));
var log = createLogger(2, colorize("LOG", 33 /* Yellow */));
var info = createLogger(2, colorize("INF", 32 /* Green */));
var warn = createLogger(3, colorize("WRN", 36 /* Cyan */));
var error = createLogger(4, colorize("ERR", 31 /* Red */, 1 /* Bold */));
// src/shared/attributes.ts
var Attributes = class {
constructor(attributesObs, donesObs, setAttributes) {
this.setAttributes = setAttributes;
this.attrs = /* @__PURE__ */ new Map();
this.updates = /* @__PURE__ */ new Map();
attributesObs.subscribe({
next: ({ attribute, removed }) => {
this.update(attribute, removed);
}
});
donesObs.subscribe({
next: (scopeIDs) => {
this.next(scopeIDs);
}
});
}
attribute(scopeID, key) {
let scopeMap = this.attrs.get(scopeID);
if (!scopeMap) {
scopeMap = /* @__PURE__ */ new Map();
this.attrs.set(scopeID, scopeMap);
}
let attr = scopeMap.get(key);
if (!attr) {
attr = new Attribute(this.setAttributes, scopeID, key);
scopeMap.set(key, attr);
}
return attr;
}
attributes(scopeID) {
let scopeMap = this.attrs.get(scopeID);
if (!scopeMap) {
scopeMap = /* @__PURE__ */ new Map();
this.attrs.set(scopeID, scopeMap);
}
return Array.from(scopeMap.values());
}
attributePeek(scopeID, key) {
let scopeUpdateMap = this.updates.get(scopeID);
if (scopeUpdateMap) {
const updated = scopeUpdateMap.get(key);
if (updated) {
if (typeof updated === "boolean") {
return;
} else {
if (!updated.val) {
return;
} else {
const attr2 = new Attribute(this.setAttributes, scopeID, key);
attr2._update(updated);
return attr2;
}
}
}
}
let scopeMap = this.attrs.get(scopeID);
if (!scopeMap) {
return;
}
let attr = scopeMap.get(key);
if (!attr) {
return;
}
if (attr.value === void 0) {
return;
}
return attr;
}
nextAttributeValue(scopeID, key) {
const attr = this.attributePeek(scopeID, key);
if (!attr) {
return;
}
return attr.value;
}
update(attr, removed) {
let nodeID = attr.nodeID;
if (!nodeID) {
if (!attr.node?.id) {
error(`new attribute without node ID`);
return;
}
nodeID = attr.node.id;
}
let scopeMap = this.updates.get(nodeID);
if (!scopeMap) {
scopeMap = /* @__PURE__ */ new Map();
this.updates.set(nodeID, scopeMap);
}
if (removed) {
scopeMap.set(attr.key, true);
} else {
let key = attr.key;
if (attr.index !== void 0 && attr.index !== null) {
key = `${key}[${attr.index}]`;
}
scopeMap.set(key, attr);
}
}
scopeWasUpdated(scopeID) {
if (!scopeID) {
return false;
}
return this.updates.has(scopeID);
}
next(scopeIDs) {
for (const [scopeID, attrs] of this.updates) {
if (!scopeIDs.includes(scopeID)) {
continue;
}
let scopeMap = this.attrs.get(scopeID);
if (!scopeMap) {
scopeMap = /* @__PURE__ */ new Map();
this.attrs.set(scopeID, scopeMap);
}
for (const [key, attrOrDel] of attrs) {
if (typeof attrOrDel === "boolean") {
let attr = scopeMap.get(key);
if (attr) {
attr._update(void 0);
}
} else {
let attr = scopeMap.get(attrOrDel.key);
if (!attr) {
attr = new Attribute(this.setAttributes, scopeID, attrOrDel.key);
scopeMap.set(attrOrDel.key, attr);
}
attr._update(attrOrDel);
}
}
}
for (const scopeID of scopeIDs) {
this.updates.delete(scopeID);
}
}
};
var Attribute = class {
constructor(setAttributes, scopeID, key) {
this.setAttributes = setAttributes;
this.scopeID = scopeID;
this.key = key;
this.val = new import_rxjs.BehaviorSubject(void 0);
}
get id() {
return this.attr?.id;
}
get createdAt() {
return this.attr ? new Date(this.attr.createdAt) : null;
}
get obs() {
return this.val;
}
get value() {
return this.val.getValue();
}
get nodeID() {
return this.scopeID;
}
// items returns the attribute changes for the current attribute, if it is a
// vector. Otherwise it returns null;
get items() {
if (!this.attrs) {
return null;
}
return this.attrs;
}
set(value, ao) {
const attrProps = this._prepSet(value, ao);
if (!attrProps) {
return;
}
this.setAttributes([attrProps]);
trace(`SET ${this.key} = ${value} (${this.scopeID})`);
}
_prepSet(value, ao, item) {
if (ao?.append !== void 0 && ao.index !== void 0) {
error(`cannot set both append and index`);
throw new Error(`cannot set both append and index`);
}
const serVal = JSON.stringify(value);
if (!item && (ao?.index !== void 0 || ao?.append)) {
let index = ao.index || 0;
if (ao?.append) {
index = this.attrs?.length || 0;
}
if (!this.attrs) {
this.attrs = [];
}
if (!this.attrs[index]) {
this.attrs[index] = new Attribute(
this.setAttributes,
this.scopeID,
this.key
);
} else {
const existing = this.attrs[index];
if (existing && existing.serVal === serVal) {
return;
}
}
this.attrs[index]._prepSet(value, ao, true);
const v = this._recalcVectorVal();
this.val.next(v);
} else {
if (this.serVal === serVal) {
return;
}
this.val.next(value);
}
this.serVal = serVal;
const attrProps = {
key: this.key,
nodeID: this.scopeID,
val: serVal
};
if (ao) {
attrProps.private = ao.private;
attrProps.protected = ao.protected;
attrProps.immutable = ao.immutable;
attrProps.ephemeral = ao.ephemeral;
attrProps.append = ao.append;
attrProps.index = ao.index;
}
return attrProps;
}
_recalcVectorVal() {
return this.attrs.map(
(a) => !a || a.val == void 0 ? null : a.value || null
);
}
// internal only
_update(attr, item) {
if (attr && this.attr && this.attr.id === attr.id) {
return;
}
if (attr && attr.vector && !item) {
if (attr.index === void 0) {
error(`vector attribute missing index`);
return;
}
if (this.attrs == void 0) {
this.attrs = [];
}
while (this.attrs.length < attr.index + 1) {
const newAttr2 = new Attribute(
this.setAttributes,
this.scopeID,
this.key
);
this.attrs.push(newAttr2);
}
const newAttr = new Attribute(this.setAttributes, this.scopeID, this.key);
newAttr._update(attr, true);
this.attrs[attr.index] = newAttr;
const value2 = this._recalcVectorVal();
this.val.next(value2);
return;
}
this.attr = attr;
this.serVal = attr?.val === void 0 || attr?.val === null ? "" : attr.val;
let value = void 0;
if (this.attr?.val) {
value = JSON.parse(this.attr.val);
}
this.val.next(value);
}
};
// src/shared/globals.ts
var import_rxjs2 = require("rxjs");
var Globals = class {
constructor(globals) {
this.attrs = /* @__PURE__ */ new Map();
this.updates = /* @__PURE__ */ new Map();
this.self = new import_rxjs2.BehaviorSubject(void 0);
globals.subscribe({
next: ({ attribute, done }) => {
if (attribute) {
let val = void 0;
if (attribute.val) {
val = JSON.parse(attribute.val);
}
this.updates.set(attribute.key, val);
}
if (done) {
for (const [key, val] of this.updates) {
this.obs(key).next(val);
}
this.updates.clear();
if (this.self) {
this.self.next(this);
}
}
}
});
}
get(key) {
const o = this.attrs.get(key);
if (o) {
return o.getValue();
}
return void 0;
}
obs(key) {
let o = this.attrs.get(key);
if (!o) {
o = new import_rxjs2.BehaviorSubject(void 0);
this.attrs.set(key, o);
}
return o;
}
};
// src/player/provider.ts
var import_rxjs3 = require("rxjs");
var TajribaProvider = class {
constructor(changes, globals, setAttributes) {
this.globals = globals;
this.setAttributes = setAttributes;
this.scopes = new import_rxjs3.Subject();
this.attributes = new import_rxjs3.Subject();
this.participants = new import_rxjs3.Subject();
this.steps = new import_rxjs3.Subject();
this.dones = new import_rxjs3.Subject();
let scopeIDs = [];
changes.pipe((0, import_rxjs3.groupBy)((chg) => chg?.change?.__typename)).subscribe({
next: (group) => {
switch (group.key) {
case "ScopeChange":
group.subscribe({
next: (msg) => {
if (!msg.change || msg.removed === null || msg.removed === void 0) {
trace("AttributeChange empty");
} else {
this.scopes.next({
scope: msg.change,
removed: msg.removed
});
}
if (msg.done) {
this.dones.next(scopeIDs);
}
}
});
break;
case "AttributeChange":
group.subscribe({
next: (msg) => {
if (!msg.change || msg.removed === null || msg.removed === void 0) {
trace("AttributeChange empty");
} else {
const atChange = msg.change;
scopeIDs.push(atChange.nodeID || atChange.node.id);
this.attributes.next({
attribute: atChange,
removed: msg.removed
});
}
if (msg.done) {
this.dones.next(scopeIDs);
scopeIDs = [];
}
}
});
break;
case "ParticipantChange":
group.subscribe({
next: (msg) => {
if (!msg.change || msg.removed === null || msg.removed === void 0) {
trace("ParticipantChange empty");
} else {
this.participants.next({
participant: msg.change,
removed: msg.removed
});
}
if (msg.done) {
this.dones.next([]);
}
}
});
break;
case "StepChange":
group.subscribe({
next: (msg) => {
if (!msg.change || msg.removed === null || msg.removed === void 0) {
trace("StepChange empty");
} else {
this.steps.next({
step: msg.change,
removed: msg.removed
});
}
if (msg.done) {
this.dones.next([]);
}
}
});
break;
default:
group.subscribe({
next: (change) => {
if (change.done) {
this.dones.next([]);
}
}
});
break;
}
}
});
}
};
// src/shared/scopes.ts
var import_rxjs4 = require("rxjs");
var Scopes = class {
constructor(scopesObs, donesObs, ctx, kinds2, attributes) {
this.ctx = ctx;
this.kinds = kinds2;
this.attributes = attributes;
this.scopes = /* @__PURE__ */ new Map();
// newScopes is used to track scopes that have appeared for the first time.
this.newScopes = /* @__PURE__ */ new Map();
this.scopesByKind = /* @__PURE__ */ new Map();
this.kindUpdated = /* @__PURE__ */ new Set();
scopesObs.subscribe({
next: ({ scope, removed }) => {
this.update(scope, removed);
}
});
donesObs.subscribe({
next: (scopeIDs) => {
this.next(scopeIDs);
}
});
}
scope(id) {
return this.scopes.get(id)?.getValue();
}
scopeObs(id) {
return this.scopes.get(id);
}
byKind(kind) {
let map2 = this.scopesByKind.get(kind);
if (!map2) {
map2 = /* @__PURE__ */ new Map();
this.scopesByKind.set(kind, map2);
}
return map2;
}
kindWasUpdated(kind) {
return this.kindUpdated.has(kind);
}
next(scopeIDs) {
this.kindUpdated.clear();
for (const [_, scopeSubject] of this.scopes) {
const scope = scopeSubject.getValue();
if ((scope._updated || this.attributes.scopeWasUpdated(scope.id)) && scopeIDs.includes(scope.id)) {
scope._updated = false;
scopeSubject.next(scope);
}
}
}
update(scope, removed) {
const existing = this.scopes.get(scope.id)?.getValue();
if (removed) {
if (!existing) {
warn("scopes: missing scope on removal", scope.id, scope.kind);
return;
}
existing._deleted = true;
existing._updated = true;
this.scopes.delete(scope.id);
if (!scope.kind) {
warn("scopes: scope missing kind on scope on removal");
return;
}
const kind2 = scope.kind;
this.scopesByKind.get(kind2).delete(scope.id);
this.kindUpdated.add(kind2);
return;
}
if (existing) {
existing._deleted = false;
return;
}
if (!scope.kind) {
warn("scopes: scope missing kind on scope");
return;
}
const kind = scope.kind;
const scopeClass = this.kinds[kind];
if (!scopeClass) {
warn(`scopes: unknown scope kind: ${scope.kind}`);
return;
}
const obj = this.create(scopeClass, scope);
const subj = new import_rxjs4.BehaviorSubject(obj);
this.scopes.set(scope.id, subj);
this.newScopes.set(scope.id, true);
let skm = this.scopesByKind.get(kind);
if (!skm) {
skm = /* @__PURE__ */ new Map();
this.scopesByKind.set(kind, skm);
}
skm.set(scope.id, obj);
obj._updated = true;
this.kindUpdated.add(kind);
}
create(scopeClass, scope) {
return new scopeClass(this.ctx, scope, this.attributes);
}
};
var Scope = class {
constructor(ctx, scope, attributes) {
this.ctx = ctx;
this.scope = scope;
this.attributes = attributes;
/**
* @internal
*/
this._deleted = false;
/**
* @internal
*/
this._updated = false;
}
get id() {
return this.scope.id;
}
/**
* @internal
*/
get kind() {
return this.scope.kind;
}
get(key) {
return this.attributes.attribute(this.scope.id, key).value;
}
getAttribute(key) {
return this.attributes.attribute(this.scope.id, key);
}
obs(key) {
return this.attributes.attribute(this.scope.id, key).obs;
}
set(keyOrAttributes, value, ao) {
if (typeof keyOrAttributes === "string") {
if (value === void 0) {
value = null;
}
return this.attributes.attribute(this.scope.id, keyOrAttributes).set(value, ao);
}
const nextProps = [];
for (const attr of keyOrAttributes) {
const at = this.attributes.attribute(this.scope.id, attr.key)._prepSet(attr.value, attr.ao);
if (!at) {
continue;
}
nextProps.push(at);
}
if (nextProps.length === 0) {
return;
}
this.attributes.setAttributes(nextProps);
}
append(key, value, ao) {
if (!ao) {
ao = {};
}
ao.append = true;
return this.attributes.attribute(this.scope.id, key).set(value, ao);
}
inspect() {
const attrs = this.attributes.attributes(this.scope.id);
const out = {};
for (const attr of attrs) {
out[attr.key] = attr.value;
}
return out;
}
/**
* @internal
*/
hasUpdated() {
return this._updated || this.attributes.scopeWasUpdated(this.id);
}
};
// src/player/scopes.ts
var Scopes2 = class extends Scopes {
constructor(scopesObs, donesObs, ctx, kinds2, attributes, steps) {
super(scopesObs, donesObs, ctx, kinds2, attributes);
this.steps = steps;
}
create(scopeClass, scope) {
return new scopeClass(
this.ctx,
scope,
this,
this.attributes,
this.steps
);
}
};
var Scope2 = class extends Scope {
constructor(ctx, scope, scopes, attributes, steps) {
super(ctx, scope, attributes);
this.scopes = scopes;
this.steps = steps;
}
scopeByKey(key) {
const id = this.get(key);
if (!id || typeof id !== "string") {
return;
}
return this.scopes.scope(id);
}
ticker(id) {
return this.steps.step(id);
}
tickerByKey(key) {
const id = this.get(key);
if (!id || typeof id !== "string") {
return;
}
return this.ticker(id);
}
};
// src/player/steps.ts
var import_rxjs5 = require("rxjs");
var scheduled = [];
var mockNow = null;
function pnow() {
if (mockNow !== null) {
return mockNow;
} else {
return performance.now();
}
}
function timeout(callback, ms) {
if (mockNow !== null) {
const schd = {
cb: callback,
from: mockNow,
dur: ms
};
scheduled.push(schd);
} else {
setTimeout(callback, ms);
}
}
var Step = class {
constructor(step, ticker) {
this.running = false;
this.ticker = new import_rxjs5.BehaviorSubject(void 0);
this.startAt = 0;
this.endAt = 0;
ticker.pipe((0, import_rxjs5.map)(this.recalc.bind(this))).subscribe({
next: (val) => {
this.ticker.next(val);
}
});
this._update(step);
}
recalc(t) {
if (!this.running) {
return void 0;
}
return {
started: t >= this.startAt,
ended: t >= this.endAt,
elapsed: Math.round(t - this.startAt),
remaining: Math.round(this.endAt - t),
duration: this.endAt - this.startAt
};
}
obs() {
return this.ticker;
}
get current() {
return this.recalc(pnow());
}
// internal only
_update(step) {
if (!step.running) {
this.running = false;
this.ticker.next(void 0);
return;
}
if (step.elapsed === null || step.remaining === null || step.elapsed === void 0 || step.remaining === void 0) {
this.running = false;
return;
}
const now = pnow();
this.startAt = now - step.elapsed * 1e3;
this.endAt = now + step.remaining * 1e3;
this.running = step.elapsed >= 0 && step.remaining >= 0;
this.ticker.next(this.recalc(now));
}
// internal only
_stop() {
this.running = false;
this.ticker.next(void 0);
}
};
var Steps = class {
constructor(stepsObs, donesObs) {
this.steps = /* @__PURE__ */ new Map();
this.updates = /* @__PURE__ */ new Map();
this._hadUpdates = false;
stepsObs.subscribe({
next: ({ step, removed }) => {
this.update(step, removed);
}
});
donesObs.subscribe({
next: () => {
this.next();
}
});
this.ticker = new import_rxjs5.BehaviorSubject(Math.floor(pnow()));
const controller = new AbortController();
timerInterval(1e3, controller.signal, (t) => {
this.ticker.next(t);
});
}
step(stepID) {
return this.steps.get(stepID);
}
hadUpdates() {
const hadUpdates = this._hadUpdates;
this._hadUpdates = false;
return hadUpdates;
}
update(step, removed) {
if (removed) {
this.updates.set(step.id, true);
} else {
this.updates.set(step.id, step);
}
this._hadUpdates = true;
}
next() {
for (const [id, stepOrDel] of this.updates) {
let step = this.steps.get(id);
if (typeof stepOrDel === "boolean") {
if (step) {
step._stop();
this.steps.delete(id);
}
} else {
if (!step) {
step = new Step(stepOrDel, this.ticker);
this.steps.set(id, step);
}
step._update(stepOrDel);
}
}
this.updates.clear();
}
};
var root = typeof self === "object" && self.self == self ? self : typeof global === "object" && global.global == global ? global : {};
if (!root["requestAnimationFrame"]) {
root["requestAnimationFrame"] = (cb) => cb(pnow());
}
function timerInterval(ms = 1e3, signal, callback) {
const start = Math.floor(pnow() / 1e3) * 1e3;
function frame(time) {
if (signal.aborted)
return;
callback(time);
scheduleFrame(time);
}
function scheduleFrame(time) {
const elapsed = time - start;
const roundedElapsed = Math.round(elapsed / ms) * ms;
const targetNext = start + roundedElapsed + ms;
const delay = targetNext - pnow();
timeout(() => requestAnimationFrame(frame), delay);
}
scheduleFrame(start);
}
// src/player/utils.ts
var isDevelopment = process.env.NODE_ENV === "development";
var isProduction = process.env.NODE_ENV === "production";
var isTest = process.env.NODE_ENV === "test";
var createNewParticipant = (key = "participantKey") => {
const url = new URL(document.location.href);
url.searchParams.set(key, (/* @__PURE__ */ new Date()).getTime().toString());
window.open(url.href, "_blank")?.focus();
};
// src/player/classic/classic.ts
var import_rxjs6 = require("rxjs");
var endedStatuses = ["ended", "terminated", "failed"];
var Game = class extends Scope2 {
get hasEnded() {
return endedStatuses.includes(this.get("status"));
}
get stage() {
return this.scopeByKey("stageID");
}
get round() {
return this.stage?.round;
}
};
var Player = class extends Scope2 {
get game() {
const { game } = this.ctx;
if (!game) {
return;
}
const key = `playerGameID-${game.id}`;
return this.scopeByKey(key);
}
get round() {
const { stage } = this.ctx;
if (!stage) {
return;
}
const { round } = stage;
if (!round) {
return;
}
const key = `playerRoundID-${round.id}`;
return this.scopeByKey(key);
}
get stage() {
const { stage } = this.ctx;
if (!stage) {
return;
}
const key = `playerStageID-${stage.id}`;
return this.scopeByKey(key);
}
hasUpdated() {
if (super.hasUpdated()) {
return true;
}
return Boolean(
this.round?.hasUpdated() || this.stage?.hasUpdated() || this.game?.hasUpdated()
);
}
};
var PlayerGame = class extends Scope2 {
};
var PlayerRound = class extends Scope2 {
};
var PlayerStage = class extends Scope2 {
};
var Round = class extends Scope2 {
};
var Stage = class extends Scope2 {
get round() {
return this.scopeByKey("roundID");
}
get timer() {
return this.tickerByKey("timerID");
}
};
var Context = class {
};
var kinds = {
game: Game,
player: Player,
playerGame: PlayerGame,
playerRound: PlayerRound,
playerStage: PlayerStage,
round: Round,
stage: Stage
};
function EmpiricaClassic(participantID, provider) {
const attributesDones = new import_rxjs6.Subject();
const scopesDones = new import_rxjs6.Subject();
const ctx = new Context();
const attributes = new Attributes(
provider.attributes,
attributesDones,
provider.setAttributes
);
const steps = new Steps(
provider.steps,
provider.dones
);
const scopes = new Scopes2(
provider.scopes,
scopesDones,
ctx,
kinds,
attributes,
steps
);
const participantIDs = /* @__PURE__ */ new Set();
const glob = new Globals(provider.globals);
const ret = {
game: new import_rxjs6.BehaviorSubject(void 0),
player: new import_rxjs6.BehaviorSubject(void 0),
players: new import_rxjs6.BehaviorSubject(void 0),
round: new import_rxjs6.BehaviorSubject(void 0),
stage: new import_rxjs6.BehaviorSubject(void 0),
globals: glob.self
};
provider.participants.subscribe({
next: ({ participant, removed }) => {
if (removed) {
if (participantIDs.has(participant.id)) {
participantIDs.delete(participant.id);
}
} else {
if (!participantIDs.has(participant.id)) {
participantIDs.add(participant.id);
}
}
}
});
let scopesUpdated = /* @__PURE__ */ new Set();
provider.attributes.subscribe({
next: (attr) => {
const nodeID = attr.attribute.node?.id || attr.attribute.nodeID;
if (!nodeID) {
return;
}
scopesUpdated.add(nodeID);
}
});
provider.dones.subscribe({
next: () => {
const current = getCurrent(ret);
const updated = getMainObjects(participantID, scopes, attributes);
ctx.game = updated.game;
ctx.stage = updated.stage;
if (scopeChanged(current.game, updated.game)) {
ret.game.next(updated.game);
}
if (scopeChanged(current.player, updated.player)) {
ret.player.next(updated.player);
}
if (scopeChanged(current.round, updated.round)) {
ret.round.next(updated.round);
}
if (scopeChanged(current.stage, updated.stage) || steps.hadUpdates()) {
ret.stage.next(updated.stage);
}
let playersChanged = false;
const players = [];
for (let i = 0; i < (updated.players || []).length; i++) {
let p = updated.players[i];
if (p) {
const partID = attributes.nextAttributeValue(
p.id,
"participantID"
);
if (!participantIDs.has(partID)) {
p = void 0;
}
}
if (!playersChanged && scopeChanged(p, (current.players || [])[i])) {
playersChanged = true;
}
if (!playersChanged && scopeChanged(p?.stage, (current.players || [])[i]?.stage)) {
playersChanged = true;
}
if (!playersChanged && scopeChanged(p?.round, (current.players || [])[i]?.round)) {
playersChanged = true;
}
if (!playersChanged && scopeChanged(p?.game, (current.players || [])[i]?.game)) {
playersChanged = true;
}
if (p) {
players.push(p);
}
}
if (playersChanged) {
ret.players.next(players);
}
const scopeIDs = Array.from(scopesUpdated);
scopesDones.next(scopeIDs);
attributesDones.next(scopeIDs);
scopesUpdated.clear();
}
});
return ret;
}
function scopeChanged(current, updated) {
if (!current && !updated) {
if (current === void 0 && updated === null) {
return true;
}
return false;
}
if (!current || !updated) {
return true;
}
return current.id !== updated.id || updated.hasUpdated();
}
function getCurrent(ctx) {
return {
game: ctx.game.getValue(),
player: ctx.player.getValue(),
round: ctx.round.getValue(),
stage: ctx.stage.getValue(),
players: ctx.players.getValue()
};
}
function getMainObjects(participantID, scopes, attributes) {
const players = Array.from(scopes.byKind("player").values());
players.sort();
const res = {
players,
game: null,
player: null,
round: null,
stage: null
};
if (players.length === 0) {
return res;
}
res.player = players.find((p) => {
const pID = attributes.nextAttributeValue(p.id, "participantID");
return pID === participantID;
});
if (!res.player) {
return res;
}
res.game = nextScopeByKey(scopes, attributes, res.player, "gameID");
if (!res.game) {
return res;
}
for (const player of players || []) {
const key = `playerGameID-${res.game.id}`;
if (!nextScopeByKey(scopes, attributes, player, key)) {
return res;
}
}
res.stage = nextScopeByKey(scopes, attributes, res.game, "stageID");
if (!res.stage) {
return res;
}
for (const player of players || []) {
const key = `playerStageID-${res.stage.id}`;
if (!nextScopeByKey(scopes, attributes, player, key)) {
delete res.stage;
return res;
}
}
res.round = nextScopeByKey(scopes, attributes, res.stage, "roundID");
if (!res.round) {
return res;
}
for (const player of players || []) {
const key = `playerRoundID-${res.round.id}`;
if (!nextScopeByKey(scopes, attributes, player, key)) {
delete res.stage;
delete res.round;
return res;
}
}
return res;
}
function nextScopeByKey(scopes, attributes, scope, key) {
const id = attributes.nextAttributeValue(scope.id, key);
if (!id || typeof id !== "string") {
return null;
}
return scopes.scope(id) || null;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Attribute,
Attributes,
EmpiricaClassic,
Game,
Globals,
Player,
PlayerGame,
PlayerRound,
PlayerStage,
Round,
Scope,
Scopes,
Stage,
Step,
Steps,
TajribaProvider,
createNewParticipant,
isDevelopment,
isProduction,
isTest
});
//# sourceMappingURL=player-classic.cjs.map