Skip to content

Commit 8822959

Browse files
committed
Added TODO and removed create/destroy from sequence for now.
1 parent 9484560 commit 8822959

2 files changed

Lines changed: 98 additions & 23 deletions

File tree

Sequence/Sequence.js

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
// Start recording on selected/listed objects.
1919
// Flags:
2020
// --attrs <a,b,...> Only record the listed attributes (default: all)
21-
// --create [n] Also capture the next N object creations
2221
//
2322
// !sequence stop [ignore-selected] [obj_id...]
2423
// Stop recording and save automatically if a name was given.
@@ -1515,7 +1514,7 @@ var Sequence = Sequence || (() => {
15151514
return `?{Easing|${entries.join('|')}}`;
15161515
};
15171516

1518-
const TYPE_QUERY = '?{Type|change|command|create|destroy}';
1517+
const TYPE_QUERY = '?{Type|change|command}';
15191518

15201519
const generateHandoutHtml = (name, recording, attrCols) => {
15211520
const { objectType = 'graphic', duration = 0, notes = '' } = recording;
@@ -2042,10 +2041,10 @@ var Sequence = Sequence || (() => {
20422041
}
20432042
});
20442043

2045-
// Include command, create, destroy, and change rows with deltas
2044+
// Include command and change rows with deltas
20462045
// Also include blank change rows (user may have added them manually)
20472046
if (Object.keys(kf.deltas).length > 0 ||
2048-
kf.type === 'command' || kf.type === 'create' || kf.type === 'destroy' ||
2047+
kf.type === 'command' ||
20492048
kf.type === 'change') {
20502049
track.keyframes.push(kf);
20512050
}
@@ -3664,7 +3663,7 @@ var Sequence = Sequence || (() => {
36643663
replyError(msg, 'Usage: !sequence set-type <name> <time> <type>');
36653664
return;
36663665
}
3667-
const validTypes = ['change', 'command', 'create', 'destroy'];
3666+
const validTypes = ['change', 'command'];
36683667
if (!validTypes.includes(newType)) {
36693668
replyError(msg, `Invalid type "${newType}". Valid: ${validTypes.join(', ')}`);
36703669
return;
@@ -4225,7 +4224,7 @@ var Sequence = Sequence || (() => {
42254224
// Guard against recursive handout change events when we write back
42264225
const handoutWriting = new Set();
42274226

4228-
const VALID_TYPES = new Set(['change', 'command', 'create', 'destroy']);
4227+
const VALID_TYPES = new Set(['change', 'command']);
42294228

42304229
/**
42314230
* Validate a parsed recording. Returns an array of error strings.
@@ -4381,21 +4380,6 @@ var Sequence = Sequence || (() => {
43814380
});
43824381
};
43834382

4384-
const onDestroyObject = (obj) => {
4385-
const id = obj.get('id');
4386-
const session = activeSessions[id];
4387-
if (session) {
4388-
session.keyframes.push({
4389-
time: Date.now() - session.startTime,
4390-
type: 'destroy',
4391-
deltas: {},
4392-
easings: {},
4393-
});
4394-
stopRecording(id);
4395-
}
4396-
stopPlayback(id);
4397-
};
4398-
43994383
// =========================================================================
44004384
// Help text
44014385
// =========================================================================
@@ -5172,8 +5156,6 @@ if (opacityReg) opacityReg.set(obj, 0.5);`
51725156
.filter(Boolean)
51735157
);
51745158
watchProps.forEach(prop => on(`change:graphic:${prop}`, onObjectChanged));
5175-
5176-
on('destroy:graphic', onDestroyObject);
51775159
};
51785160

51795161
// =========================================================================

Sequence/TODO.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Sequence — Deferred / TODO
2+
3+
## Deferred: Token Create/Destroy
4+
5+
Token creation and destruction are not currently supported. Two design approaches are worth considering:
6+
7+
**Approach A — Per-token keyframe types (`create`/`destroy`)**
8+
Add `create` and `destroy` as keyframe types on a single-token recording. On playback of `create`, call `createObj` using identity data (imgsrc, size, layer, etc.) stored at record time. On playback of `destroy`, call `obj.remove()`. Recording would capture token deletion via `destroy:graphic` and token creation via an `add:graphic` listener (or `--create [n]` flag). Simple extension of the current model but awkward when multiple tokens are involved.
9+
10+
**Approach B — Page-level recording**
11+
A single handout captures an entire scene: one track per token, each track starting/ending at the timestamps when that token existed. Token appearance and disappearance fall out naturally from track start/end times rather than explicit keyframe types. Better fit for cutscene-style multi-token animations but requires a significant rethink of playback targeting (currently one recording plays on one token).
12+
13+
**Why deferred:** Choreograph will introduce multi-token orchestration and may clarify which approach fits better. Revisit after Choreograph is implemented.
14+
15+
---
16+
17+
## Deferred: Command Keyframe — Token Selection
18+
19+
When firing a command keyframe during playback, there is currently no mechanism to select the playback target token so that receiving scripts can act on it via `msg.selected`.
20+
21+
**Design sketch:**
22+
- Add a `sel-command` keyframe type (or `selectToken: true` flag) to indicate the playback target should be "selected" when the command fires
23+
- `!sequence command --select` records with this flag set
24+
- `!sequence add-command --select` sets it on a saved recording
25+
- On playback, construct the outgoing message with the token ID injected into `selected` (or use SelectManager's forwarding mechanism if the target script reads `msg.selected` directly)
26+
- Selection must happen before the command fires
27+
28+
**Multi-selection note:** Some scripts behave differently (or require) multiple tokens in `msg.selected` simultaneously. If combined with a `sync` keyframe, all tokens in the playback group could be injected into `selected` for the subsequent command. This overlaps with the sync/barrier design below.
29+
30+
**Why deferred:** Most scripts can use `{{tokenId}}` substitution instead. Defer until there's a concrete script that requires `msg.selected`.
31+
32+
---
33+
34+
## Deferred: Sync Keyframe Type (Multi-Token Barrier)
35+
36+
When the same recording plays on multiple tokens simultaneously, a `sync` keyframe type would act as a general-purpose barrier: all tokens in the playback group pause at the sync point until everyone arrives, then all proceed together.
37+
38+
**Design sketch:**
39+
- Add `sync` as a keyframe type (alongside `change` and `command`)
40+
- Sessions started from the same `!sequence play` invocation share a playback group ID
41+
- When a session reaches a `sync` row, it pauses until all sessions in the group have reached (or passed) the same sync point
42+
- Configurable timeout with "proceed anyway" fallback to avoid deadlocks
43+
- Whatever follows the sync row (command, change, etc.) benefits from the coordination without needing special flags
44+
45+
**Use cases:**
46+
- Grouped commands: `sync` row followed by `command` row — all tokens are coordinated before the command fires
47+
- Synchronized motion: `sync` before a position change ensures tokens move in unison despite non-deterministic timing earlier
48+
- Combined with token selection: at a sync point, all group members could be injected into `msg.selected` for the next command
49+
50+
**Why deferred:** Requires the "playback group" concept (sessions sharing a group ID). No concrete use case yet. Revisit after Choreograph.
51+
52+
---
53+
54+
## Note: Command Side Effects and Double-Recording
55+
56+
Roll20's `change:graphic` events are **only fired for player/GM-initiated changes**. API-initiated `obj.set()` calls do NOT trigger change events. This means if a command keyframe fires during recording and the receiving script modifies the token via `obj.set()`, Sequence will not capture those changes as duplicate keyframes — there is no double-recording problem for core attributes.
57+
58+
The only scenario where double-recording could occur is with **virtual attributes** where an extension calls `Sequence.notifyChange()` directly in response to a command. Two approaches to handle this without burdening extension authors:
59+
60+
1. **Command prefix registration** — extensions register the prefix strings their commands start with (e.g. `"!myscript"`). When Sequence is about to invoke a recorded command matching that prefix, it temporarily unlinks that extension's `notifyChange` calls until the command's effects settle.
61+
62+
2. **Last-command introspection API** — expose `Sequence.getLastCommand()` or similar, returning the last recorded command and its invocation timestamp. Extensions can check this themselves if they want to conditionally skip `notifyChange` calls triggered by a Sequence-fired command.
63+
64+
Either approach (or both) makes the system safe for virtual attributes without requiring extension authors to manually track suppression state.
65+
66+
---
67+
68+
## Planned: `t` Variable in Expressions (Orbit / Cycle Animations)
69+
70+
Add a normalized time variable `t` (0–1, representing elapsed fraction of the current loop cycle) to the expression scope so users can write orbit-style animations:
71+
72+
```
73+
left: =orig_left + cos(t * 2 * PI) * distance
74+
top: =orig_top + sin(t * 2 * PI) * distance
75+
```
76+
77+
Needed for `!sequence generate orbit` and MovementAnimations parity. TBD whether this lives in Sequence's expression scope or in Choreograph.
78+
79+
---
80+
81+
## Deferred: Parameterized Recordings / `!sequence generate`
82+
83+
Rather than a `generate` command that creates handouts from parameters, a better approach may be parameterized recordings — handouts where expressions reference parameters supplied at play time (e.g. `--param distance=140`). This would allow shipping pre-built templates (spin, hover, orbit) that users can customize without editing the handout.
84+
85+
However, Choreograph already has a `{{paramName}}` substitution system and is the natural place for per-token parameter customization. Designing Sequence's parameter system before Choreograph exists risks duplication or conflict.
86+
87+
**Plan:** Implement `t` (normalized loop time) in Sequence now. Defer full parameter substitution until Choreograph clarifies the boundary between the two systems. Ship example handouts (spin, hover, orbit using `t`) after `t` is implemented.
88+
89+
---
90+
91+
## Planned: MovementAnimations Parity
92+
93+
Spin and hover are straightforward with current Sequence. Orbit requires the `t` variable or Choreograph. Wave stagger offsets (the 8 sort orders from MovementAnimations) belong in Choreograph.

0 commit comments

Comments
 (0)