@@ -953,149 +953,115 @@ export function setUndoRedoOverride() {
953953 } , 1000 ) ; // Reset after 1 second (this timeout length might need tuning for reliability).
954954}
955955
956+ /**
957+ * Flushes the event buffer for a given group ID, sending all buffered events
958+ * as a single atomic transaction.
959+ * @param {string } groupId The ID of the event group to flush.
960+ */
961+ function flushEventBuffer ( groupId ) {
962+ const buffer = constants . mutableRefs . eventTransactionBuffer ;
963+ if ( buffer . has ( groupId ) ) {
964+ const eventBatch = buffer . get ( groupId ) ;
965+ if ( eventBatch . length > 0 ) {
966+ constants . mutableRefs . ydoc . transact ( ( ) => {
967+ constants . mutableRefs . yEvents . push ( [ eventBatch ] ) ;
968+ if ( constants . debugging ) console . log ( `Collab Send: Flushed ${ eventBatch . length } events for group ${ groupId } .` ) ;
969+ } , constants . LOCAL_EVENT_SYNC_ORIGIN ) ;
970+ }
971+ buffer . delete ( groupId ) ;
972+ }
973+ }
974+
956975/**
957976 * Central handler for all Blockly/VM events that need to be broadcast for collaboration.
958- * This function filters out non-relevant events and prepares the payload
959- * before pushing it to the Yjs shared event array (`constants.mutableRefs.yEvents`).
977+ * This function filters out non-relevant events, prepares the payload, and then
978+ * either sends it immediately (for isolated events) or buffers it to be sent
979+ * in a transaction with other events from the same user action.
960980 * @param {object } event - The Blockly event object.
961981 */
962982export function handleBlocklyEventForCollaboration ( event ) {
963- // console.log("Blockly Event:", event.type, event); // Debug log for all events.
983+ if ( ! constants . mutableRefs . BlocklyInstance || ! constants . mutableRefs . BlocklyInstance . Events . isEnabled ( ) ) return ;
964984
965- // Special handling for 'dragOutside' events: set a flag.
966985 if ( event . type === 'dragOutside' ) {
967986 constants . mutableRefs . dragOutsideStarted = true ;
968- return ; // Do not process 'dragOutside' as a regular event.
987+ return ;
969988 }
970989
971- // Skip if Blockly events are globally disabled (e.g., during remote sync).
972- if ( ! constants . mutableRefs . BlocklyInstance || ! constants . mutableRefs . BlocklyInstance . Events . isEnabled ( ) ) return ;
973-
974990 const workspace = constants . mutableRefs . BlocklyInstance . getMainWorkspace ( ) ;
975- // // Optional: Ensure event is for the main workspace. Currently commented out.
976- // if (!workspace || event.workspaceId !== workspace.id) return;
977-
978- // Filter out UI-only events that do not represent actual model changes.
979- // `event.recordUndo` is a good proxy for events that modify the project state.
980- // Explicitly allow events related to drag-outside (delete/move) or backpack/undo/redo overrides.
981- const isDragOutsideMoveOrDelete = constants . mutableRefs . dragOutsideStarted &&
982- [ "move" , "delete" ] . includes ( event . type ) ;
983-
984- const isBackpackOverride = backpackInsertOverride &&
985- [ "move" , "create" ] . includes ( event . type ) ;
986991
992+ const isDragOutsideMoveOrDelete = constants . mutableRefs . dragOutsideStarted && [ "move" , "delete" ] . includes ( event . type ) ;
993+ const isBackpackOverride = backpackInsertOverride && [ "move" , "create" ] . includes ( event . type ) ;
987994 const isUndoRedoOverride = ( undoRedoOverride && [ "create" , "move" , "delete" , "var_create" ] . includes ( event . type ) ) ||
988- ( event . type === 'var_rename' && event . varId && event . oldName && event . newName ) ; // var_rename might not have recordUndo but needs sync.
995+ ( event . type === 'var_rename' && event . varId && event . oldName && event . newName ) ;
989996
990- // If an event is not undoable AND not covered by the overrides, skip it.
991997 if ( ! event . recordUndo && ! ( isDragOutsideMoveOrDelete || isBackpackOverride || isUndoRedoOverride ) ) {
992- // console.log("Collab Send: Skipping non-undoable event:", event.type, event.element || '', event); // Debug log.
993998 return ;
994999 } else if ( ! event . recordUndo && constants . mutableRefs . dragOutsideStarted ) {
995- // Reset dragOutsideStarted flag if it was set and this event was part of a drag-outside sequence.
9961000 constants . mutableRefs . dragOutsideStarted = false ;
9971001 }
9981002
999- // Filter out events for blocks within the flyout (toolbox) or mutator, as these are temporary UI elements.
10001003 if ( event . type === constants . mutableRefs . BlocklyInstance . Events . CREATE ) {
10011004 const block = workspace . getBlockById ( event . blockId ) ;
10021005 if ( block ?. isInFlyout || block ?. isInMutator ) return ;
10031006 }
10041007
1005- // --- Determine Editing Target Context ---
1006- const targetName = helper . getCurrentEditingTargetName ( ) ; // Get the name of the currently edited target.
1007- let targetId = null ;
1008- const editingTarget = constants . mutableRefs . vm . runtime . getEditingTarget ( ) ;
1009- if ( editingTarget ) {
1010- targetId = editingTarget . id ;
1011- }
1012-
1008+ const targetName = helper . getCurrentEditingTargetName ( ) ;
10131009 let eventJson = null ;
10141010
1015- // --- Prepare Payload for Variable Events ---
10161011 if ( event . type === constants . mutableRefs . BlocklyInstance . Events . VAR_CREATE ) {
1017- if ( typeof event . varId !== 'string' || typeof event . varName !== 'string' ) {
1018- console . warn ( "Collab Send [VarCreate]: Invalid variable ID or name:" , event ) ;
1019- return ;
1020- }
1021- // Determine the target name for local variables, or null for global.
1012+ if ( typeof event . varId !== 'string' || typeof event . varName !== 'string' ) return ;
10221013 const varTargetName = event . isLocal ? ( constants . mutableRefs . vm . runtime . getEditingTarget ( ) ?. getName ( ) || targetName ) : null ;
1023- if ( event . isLocal && ! varTargetName ) {
1024- console . warn ( "Collab Send [VarCreate]: Local variable event but could not get target name. Skipping." , event ) ;
1025- return ;
1026- }
1027- eventJson = {
1028- type : "var_create" ,
1029- isCloud : event . isCloud ,
1030- isLocal : event . isLocal ,
1031- id : event . varId , // Send original ID as a hint for receiver.
1032- name : event . varName ,
1033- varType : event . varType ,
1034- targetName : varTargetName // Name of the target sprite if local, else null.
1035- } ;
1014+ if ( event . isLocal && ! varTargetName ) return ;
1015+ eventJson = { type : "var_create" , isCloud : event . isCloud , isLocal : event . isLocal , id : event . varId , name : event . varName , varType : event . varType , targetName : varTargetName } ;
10361016 } else if ( event . type === constants . mutableRefs . BlocklyInstance . Events . VAR_RENAME ) {
1037- if ( typeof event . varId !== 'string' || typeof event . oldName !== 'string' || typeof event . newName !== 'string' ) {
1038- console . warn ( "Collab Send [VarRename]: Invalid data for rename:" , event ) ;
1039- return ;
1040- }
1041- eventJson = {
1042- type : "var_rename" ,
1043- originalVarId : event . varId ,
1044- oldName : event . oldName ,
1045- newName : event . newName ,
1046- ambiguityFix : { // Provide additional context to help receiver identify the variable uniquely.
1047- varType : event . variable . type ,
1048- isCloud : event . variable . isCloud ,
1049- isLocal : event . variable . isLocal ,
1050- }
1051- } ;
1017+ if ( typeof event . varId !== 'string' || typeof event . oldName !== 'string' || typeof event . newName !== 'string' ) return ;
1018+ eventJson = { type : "var_rename" , originalVarId : event . varId , oldName : event . oldName , newName : event . newName , ambiguityFix : { varType : event . variable . type , isCloud : event . variable . isCloud , isLocal : event . variable . isLocal } } ;
10521019 } else if ( event . type === constants . mutableRefs . BlocklyInstance . Events . VAR_DELETE ) {
1053- if ( typeof event . varId !== 'string' || typeof event . varName !== 'string' ) {
1054- console . warn ( "Collab Send [VarDelete]: Invalid data for delete:" , event ) ;
1055- return ;
1056- }
1057- eventJson = {
1058- type : "var_delete" ,
1059- originalVarId : event . varId ,
1060- varName : event . varName ,
1061- ambiguityFix : { // Provide additional context to help receiver identify the variable uniquely.
1062- varType : event . varType ,
1063- isCloud : event . isCloud ,
1064- isLocal : event . isLocal ,
1065- }
1066- } ;
1020+ if ( typeof event . varId !== 'string' || typeof event . varName !== 'string' ) return ;
1021+ eventJson = { type : "var_delete" , originalVarId : event . varId , varName : event . varName , ambiguityFix : { varType : event . varType , isCloud : event . isCloud , isLocal : event . isLocal } } ;
10671022 }
10681023
1069- // If it's not a variable event, convert the Blockly event to JSON.
10701024 if ( eventJson == null ) {
10711025 try {
10721026 eventJson = event . toJson ( ) ;
1073- // Additional check for variable events that might pass through this general `toJson` path
1074- // (e.g., if a new variable event type is introduced).
1075- if ( event . type . startsWith ( 'var_' ) && event . type !== constants . mutableRefs . BlocklyInstance . Events . VAR_RENAME && ( ! event . varId || typeof event . varName === 'undefined' ) ) {
1076- console . warn ( "Collab Send: Variable event missing ID or Name, skipping:" , event ) ;
1077- return ;
1078- }
1027+ if ( event . type . startsWith ( 'var_' ) && event . type !== constants . mutableRefs . BlocklyInstance . Events . VAR_RENAME && ( ! event . varId || typeof event . varName === 'undefined' ) ) return ;
10791028 } catch ( e ) {
10801029 console . error ( "Collab Send: Error serializing event:" , e , event ) ;
10811030 return ;
10821031 }
10831032 }
10841033
1085- // --- Broadcast the Event via Yjs ---
1086- if ( constants . mutableRefs . yEvents && constants . mutableRefs . ydoc ) {
1087- if ( constants . debugging ) console . log ( `Collab Send: Event=${ event . type } Target=${ targetName } Block=${ event . blockId || 'N/A' } VarId=${ event . varId || 'N/A' } ` ) ;
1088- // Wrap the event JSON and target context, then push to the shared Yjs array.
1089- // `constants.LOCAL_EVENT_SYNC_ORIGIN` is used to mark this as a local event.
1034+ if ( ! constants . mutableRefs . yEvents || ! constants . mutableRefs . ydoc ) {
1035+ console . warn ( "Collab Send: yEvents or ydoc not ready. Event not sent." ) ;
1036+ return ;
1037+ }
1038+
1039+ const eventPackage = {
1040+ targetName : targetName ,
1041+ event : eventJson ,
1042+ timestamp : Date . now ( )
1043+ } ;
1044+
1045+ const groupId = Blockly . Events . getGroup ( ) ;
1046+
1047+ if ( ! groupId ) {
1048+ // This is an isolated event (not part of a drag, etc.). Send it immediately in its own transaction.
10901049 constants . mutableRefs . ydoc . transact ( ( ) => {
1091- constants . mutableRefs . yEvents . push ( [ {
1092- targetName : targetName , // Context for receiver.
1093- event : eventJson ,
1094- timestamp : Date . now ( ) // Add timestamp for chronological processing.
1095- } ] ) ;
1050+ constants . mutableRefs . yEvents . push ( [ [ eventPackage ] ] ) ; // Wrap in an array to mark it as a batch of one.
1051+ if ( constants . debugging ) console . log ( `Collab Send: Event=${ event . type } sent immediately.` ) ;
10961052 } , constants . LOCAL_EVENT_SYNC_ORIGIN ) ;
10971053 } else {
1098- console . warn ( "Collab Send: yEvents or ydoc not ready. Event not sent." ) ;
1054+ // This event is part of a group. Buffer it.
1055+ const buffer = constants . mutableRefs . eventTransactionBuffer ;
1056+ clearTimeout ( constants . mutableRefs . eventFlushTimer ) ;
1057+
1058+ if ( ! buffer . has ( groupId ) ) {
1059+ buffer . set ( groupId , [ ] ) ;
1060+ }
1061+ buffer . get ( groupId ) . push ( eventPackage ) ;
1062+
1063+ // Set a timer to flush the buffer. If another event in the same group arrives, the timer will be reset.
1064+ constants . mutableRefs . eventFlushTimer = setTimeout ( ( ) => flushEventBuffer ( groupId ) , 100 ) ; // 100ms debounce delay
10991065 }
11001066}
11011067
@@ -1689,4 +1655,4 @@ export function setupCSS() {
16891655 */
16901656export function getActiveRemoteClientIDs ( ) {
16911657 return new Set ( cursorElements . keys ( ) ) ;
1692- }
1658+ }
0 commit comments