UNPKG

@empirica/core

Version:
1,241 lines (1,230 loc) 33.5 kB
"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