Skip to content

Commit 7687cb8

Browse files
committed
sync sprite transformations
1 parent 0788926 commit 7687cb8

3 files changed

Lines changed: 113 additions & 7 deletions

File tree

src/addons/addons/collaboration/helpers/helper.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as Y from 'yjs';
33
import * as OH from './observeHandlers.js';
44
import * as costumeSync from './costumeSync.js';
55
import * as soundSync from './soundSync.js';
6+
import * as transformSync from './transformSync.js';
67

78
const targetNameIdCache = new Map();
89

@@ -226,6 +227,8 @@ export function serializeSpriteForYjs(target) {
226227
yMap.set('id', target.id);
227228
yMap.set('name', target.getName());
228229
yMap.set('isStage', !!target.isStage);
230+
const transform = transformSync.serializeTransformFields(target);
231+
Object.keys(transform).forEach(key => yMap.set(key, transform[key]));
229232
return yMap;
230233
}
231234

@@ -606,15 +609,17 @@ export function performInitialSync() {
606609

607610
if (target) {
608611
newTargetList.push(target);
609-
localTargetsMap.delete(id);
612+
localTargetsMap.delete(id);
610613
if (target.getName() !== name) target.sprite.name = name;
614+
transformSync.applyTransformFromYjs(target, ySpriteMap);
611615
} else {
612616
const newSprite = new constants.mutableRefs.vm.exports.Sprite(null, vm.runtime);
613617
newSprite.name = name;
614618
target = newSprite.createClone(isStage ? 'background' : 'sprite');
615619
target.id = id;
616620
target.originalTargetId = id;
617621
vm.runtime.addTarget(target);
622+
transformSync.applyTransformFromYjs(target, ySpriteMap);
618623
newTargetList.push(target);
619624
}
620625
});
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import * as constants from './constants.js';
2+
3+
export const TRANSFORM_FIELDS = ['x', 'y', 'direction', 'size', 'rotationStyle', 'visible', 'draggable'];
4+
5+
export function serializeTransformFields(target) {
6+
return {
7+
x: target.x,
8+
y: target.y,
9+
direction: target.direction,
10+
size: target.size,
11+
rotationStyle: target.rotationStyle,
12+
visible: target.visible,
13+
draggable: target.draggable
14+
};
15+
}
16+
17+
export function applyTransformFromYjs(target, yMap) {
18+
const data = {};
19+
TRANSFORM_FIELDS.forEach(field => {
20+
const value = yMap.get(field);
21+
if (value !== undefined) data[field] = value;
22+
});
23+
24+
const isXChanged = Object.prototype.hasOwnProperty.call(data, 'x');
25+
const isYChanged = Object.prototype.hasOwnProperty.call(data, 'y');
26+
if (isXChanged || isYChanged) {
27+
target.setXY(isXChanged ? data.x : target.x, isYChanged ? data.y : target.y, true);
28+
}
29+
if (Object.prototype.hasOwnProperty.call(data, 'direction')) target.setDirection(data.direction);
30+
if (Object.prototype.hasOwnProperty.call(data, 'size')) target.setSize(data.size);
31+
if (Object.prototype.hasOwnProperty.call(data, 'rotationStyle')) target.setRotationStyle(data.rotationStyle);
32+
if (Object.prototype.hasOwnProperty.call(data, 'visible')) target.setVisible(data.visible);
33+
if (Object.prototype.hasOwnProperty.call(data, 'draggable')) target.setDraggable(data.draggable);
34+
}
35+
36+
function findSpriteMap(targetId) {
37+
const sharedSprites = constants.mutableRefs.sharedSprites;
38+
for (let i = 0; i < sharedSprites.length; i++) {
39+
const yMap = sharedSprites.get(i);
40+
if (yMap.get('id') === targetId) return yMap;
41+
}
42+
return null;
43+
}
44+
45+
const TRANSFORM_PUSH_THROTTLE_MS = 50;
46+
const pendingPushes = new Map();
47+
const throttleTimers = new Map();
48+
const lastPushTimes = new Map();
49+
50+
function flushTransformPush(targetId) {
51+
throttleTimers.delete(targetId);
52+
const properties = pendingPushes.get(targetId);
53+
pendingPushes.delete(targetId);
54+
if (!properties) return;
55+
56+
lastPushTimes.set(targetId, Date.now());
57+
constants.mutableRefs.syncingTargets.add(targetId);
58+
try {
59+
constants.mutableRefs.ydoc.transact(() => {
60+
const yMap = findSpriteMap(targetId);
61+
if (!yMap) return;
62+
Object.keys(properties).forEach(key => {
63+
if (TRANSFORM_FIELDS.includes(key)) yMap.set(key, properties[key]);
64+
});
65+
}, constants.LOCAL_EVENT_SYNC_ORIGIN);
66+
} finally {
67+
constants.mutableRefs.syncingTargets.delete(targetId);
68+
}
69+
}
70+
71+
export function handleLocalTransformChange(targetId, properties) {
72+
if (constants.mutableRefs.syncingTargets.has(targetId)) return;
73+
74+
const relevant = {};
75+
TRANSFORM_FIELDS.forEach(key => {
76+
if (Object.prototype.hasOwnProperty.call(properties, key)) relevant[key] = properties[key];
77+
});
78+
if (Object.keys(relevant).length === 0) return;
79+
80+
pendingPushes.set(targetId, Object.assign({}, pendingPushes.get(targetId), relevant));
81+
82+
if (throttleTimers.has(targetId)) return;
83+
84+
const elapsed = Date.now() - (lastPushTimes.get(targetId) || 0);
85+
if (elapsed >= TRANSFORM_PUSH_THROTTLE_MS) {
86+
flushTransformPush(targetId);
87+
} else {
88+
const delay = TRANSFORM_PUSH_THROTTLE_MS - elapsed;
89+
throttleTimers.set(targetId, setTimeout(() => flushTransformPush(targetId), delay));
90+
}
91+
}

src/addons/addons/collaboration/userscript.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as timeout from './helpers/timeout.js';
77
import * as helper from './helpers/helper.js';
88
import * as costumeSync from './helpers/costumeSync.js';
99
import * as soundSync from './helpers/soundSync.js';
10+
import * as transformSync from './helpers/transformSync.js';
1011
import * as OH from './helpers/observeHandlers.js';
1112

1213

@@ -127,21 +128,19 @@ function attachYjsProvider() {
127128
const existingTarget = runtime.getTargetById(id);
128129

129130
if (!existingTarget) {
130-
131131
const newSprite = new constants.mutableRefs.vm.exports.Sprite(null, runtime);
132132
newSprite.name = name;
133-
134133
const target = newSprite.createClone(isStage ? 'background' : 'sprite');
135134
target.id = id;
136135
target.originalTargetId = id;
137-
138136
runtime.addTarget(target);
139-
140-
helper.applyQueuedEventsForTarget(id);
137+
transformSync.applyTransformFromYjs(target, yMap);
138+
helper.applyQueuedEventsForTarget(id);
141139
} else {
142140
if (existingTarget.getName() !== name) {
143141
vm.renameSprite(id, name, false);
144142
}
143+
transformSync.applyTransformFromYjs(existingTarget, yMap);
145144
}
146145
});
147146
const localTargets = runtime.targets.filter(t => t.isOriginal);
@@ -612,14 +611,24 @@ function attachYjsProvider() {
612611
const newIndex = properties.currentCostume;
613612
const currentLocalState = constants.mutableRefs.yjsAwarenessInstance.getLocalState();
614613
const currentAsset = currentLocalState?.editingAsset;
615-
if (currentAsset && currentAsset.type === 'costume' && currentAsset.index !== newIndex) {
614+
if (currentAsset && currentAsset.type === 'costume' && currentAsset.index !== newIndex) {
616615
constants.mutableRefs.yjsAwarenessInstance.setLocalStateField('editingAsset', {
617616
...currentAsset,
618617
index: newIndex
619618
});
620619
}
621620
}
622621
}
622+
const hasTransformField = transformSync.TRANSFORM_FIELDS.some(
623+
key => Object.prototype.hasOwnProperty.call(properties, key));
624+
if (hasTransformField) {
625+
if (constants.mutableRefs.isInitialRoomSync) {
626+
constants.mutableRefs.pendingLocalEvents.push(
627+
{ kind: 'transform', targetId, eventData: properties });
628+
continue;
629+
}
630+
transformSync.handleLocalTransformChange(targetId, properties);
631+
}
623632
}
624633
};
625634

@@ -765,6 +774,7 @@ function attachYjsProvider() {
765774
if (kind === 'blocks') handleTargetBlocksChanged(targetId, eventData);
766775
else if (kind === 'variables') handleTargetVariablesChanged(targetId, eventData);
767776
else if (kind === 'comments') handleTargetCommentsChanged(targetId, eventData);
777+
else if (kind === 'transform') transformSync.handleLocalTransformChange(targetId, eventData);
768778
});
769779

770780
if (constants.mutableRefs.addon?.tab?.redux?.dispatch) {

0 commit comments

Comments
 (0)