fix: live-sync drawings/notes; cut per-frame sub cost; pen color+size

- backend: the scene PUT only broadcast to peers when annotation counts
  changed, so drawing/script-note edits saved but never synced live (only
  after a reload). Broadcast any gid that actually moved, regardless of type.
- perf: active-drawings/active-note-set recomputed fully on every playhead
  tick (active-drawings even re-resolved each annotation per mark). Split the
  bar computation into memoized ::drawing-bars/::note-bars (scene/ctx/segs
  only) and reduce per-frame work to cheap interval tests.
- perf: only subscribe drawing-layer to the boil clock when something is
  actually animating (no more 7fps idle re-renders).
- drawings: pen colour picker + stroke-size slider, still defaulting to the
  annotation colour per session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-07-01 19:29:32 -04:00
parent 9f06b51f76
commit 40ff2ecf52
4 changed files with 80 additions and 54 deletions

View file

@ -143,7 +143,8 @@ def scene(request, pk):
deleted = data.get("deleted") or []
summary = None
if changed or deleted:
groups = dict((p.scene or {}).get("groups", {}))
prev_groups = (p.scene or {}).get("groups", {})
groups = dict(prev_groups)
for gid, patch in changed.items():
if isinstance(patch, dict) and isinstance(groups.get(gid), dict):
groups[gid] = {**groups[gid], **patch}
@ -154,17 +155,23 @@ def scene(request, pk):
merged = dict(p.scene or {}, groups=groups)
p.scene, summary, counts = apply_attribution(
p.scene, merged, request.user.get_username(), timezone.now().isoformat())
if any(counts.values()): # don't log a no-op save
scene_groups = (p.scene or {}).get("groups", {})
if any(counts.values()): # history logs annotation edits only
Revision.objects.create(project=p, user=request.user, summary=summary,
annotations=annotation_layer(p.scene))
# push the attributed delta to peers viewing this project
scene_groups = (p.scene or {}).get("groups", {})
# Live sync must cover EVERY group kind, not just annotations: script
# notes and drawings aren't counted by attribution, so gating the
# broadcast on `counts` silently dropped their updates to peers (they
# only appeared on a full reload). Broadcast any gid that really moved.
touched = {gid: scene_groups[gid] for gid in changed
if gid in scene_groups and scene_groups[gid] != prev_groups.get(gid)}
removed = [gid for gid in deleted if gid in prev_groups]
if touched or removed:
async_to_sync(get_channel_layer().group_send)(
f"project_{p.pk}",
{"type": "scene.delta",
"delta": {"changed": {gid: scene_groups[gid]
for gid in changed if gid in scene_groups},
"deleted": list(deleted),
"delta": {"changed": touched,
"deleted": removed,
"by": request.user.get_username()}})
if "fps" in data:
p.fps = data["fps"]