Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/engine/variable.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Variable {
this.name = name;
this.type = type;
this.isCloud = isCloud;
this.isPersistent = false;
switch (this.type) {
case Variable.SCALAR_TYPE:
this.value = 0;
Expand Down
16 changes: 14 additions & 2 deletions src/extensions/scratch3_mesh_v2/mesh-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -615,10 +615,22 @@ class MeshV2Service {
broadcastEvent (event) {
log.info(`Mesh V2: Executing broadcastEvent for: ${event.name}`);
try {
const stage = this.runtime.getTargetForStage();
let broadcastVar = stage.lookupBroadcastByInputValue(event.name);
if (!broadcastVar) {
log.info(`Mesh V2: Creating missing broadcast message: ${event.name}`);
stage.createVariable(null, event.name, Variable.BROADCAST_MESSAGE_TYPE);
broadcastVar = stage.lookupBroadcastByInputValue(event.name);
if (broadcastVar) {
broadcastVar.isPersistent = true;
}
this.runtime.requestBlocksUpdate();
}

const args = {
BROADCAST_OPTION: {
id: null,
name: event.name
id: broadcastVar ? broadcastVar.id : null,
name: broadcastVar ? broadcastVar.name : event.name
}
};
const util = BlockUtility.lastInstance();
Expand Down
3 changes: 3 additions & 0 deletions src/virtual-machine.js
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,9 @@ class VirtualMachine extends EventEmitter {
// Anything left in messageIds is not referenced by a block, so delete it.
for (let i = 0; i < messageIds.length; i++) {
const id = messageIds[i];
if (this.runtime.getTargetForStage().variables[id].isPersistent) {
continue;
}
delete this.runtime.getTargetForStage().variables[id];
}
const globalVarMap = Object.assign({}, this.runtime.getTargetForStage().variables);
Expand Down
122 changes: 122 additions & 0 deletions test/unit/mesh_service_v2_broadcast_creation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const test = require('tap').test;
const MeshV2Service = require('../../src/extensions/scratch3_mesh_v2/mesh-service');
const BlockUtility = require('../../src/engine/block-utility');
const Variable = require('../../src/engine/variable');

const createMockBlocks = stage => ({
runtime: {
sequencer: {},
emit: () => {},
on: () => {},
off: () => {},
getTargetForStage: () => stage,
requestBlocksUpdate: () => {}
},
opcodeFunctions: {
event_broadcast: () => {}
}
});

const createMockStage = () => ({
variables: {},
lookupBroadcastMsg: function (id, name) {
for (const varId in this.variables) {
const v = this.variables[varId];
if (v.type === Variable.BROADCAST_MESSAGE_TYPE && v.name === name) {
return v;
}
}
return null;
},
lookupBroadcastByInputValue: function (name) {
for (const varId in this.variables) {
const v = this.variables[varId];
if (v.type === Variable.BROADCAST_MESSAGE_TYPE && v.name.toLowerCase() === name.toLowerCase()) {
return v;
}
}
return null;
},
createVariable: function (id, name, type) {
const varId = id || `id-${name}`;
this.variables[varId] = new Variable(varId, name, type);
}
});

// Mock BlockUtility.lastInstance()
const originalLastInstance = BlockUtility.lastInstance;
const mockUtil = {
sequencer: {}
};
BlockUtility.lastInstance = () => mockUtil;

test('MeshV2Service Broadcast Creation', t => {
t.test('broadcastEvent creates missing broadcast message', st => {
const stage = createMockStage();
const blocks = createMockBlocks(stage);
let broadcastArgs = null;
blocks.opcodeFunctions.event_broadcast = args => {
broadcastArgs = args;
};

const service = new MeshV2Service(blocks, 'node1', 'domain1');
service.groupId = 'group1';

const event = {
name: 'new message',
firedByNodeId: 'node2'
};

service.broadcastEvent(event);

// Check if message was created
const broadcastVar = stage.lookupBroadcastByInputValue('new message');
st.ok(broadcastVar, 'Broadcast message should be created');
st.equal(broadcastVar.name, 'new message');
st.equal(broadcastVar.type, Variable.BROADCAST_MESSAGE_TYPE);
st.equal(broadcastVar.isPersistent, true, 'Broadcast message should be persistent');

// Check if opcode was called with the new ID
st.ok(broadcastArgs, 'event_broadcast should be called');
st.equal(broadcastArgs.BROADCAST_OPTION.name, 'new message');
st.equal(broadcastArgs.BROADCAST_OPTION.id, broadcastVar.id);

st.end();
});

t.test('broadcastEvent uses existing broadcast message (case-insensitive)', st => {
const stage = createMockStage();
const existingVar = new Variable('existing-id', 'Existing Message', Variable.BROADCAST_MESSAGE_TYPE);
stage.variables['existing-id'] = existingVar;

const blocks = createMockBlocks(stage);
let broadcastArgs = null;
blocks.opcodeFunctions.event_broadcast = args => {
broadcastArgs = args;
};

const service = new MeshV2Service(blocks, 'node1', 'domain1');
service.groupId = 'group1';

const event = {
name: 'existing message',
firedByNodeId: 'node2'
};

service.broadcastEvent(event);

// Check if opcode was called with the existing ID and canonical name
st.ok(broadcastArgs, 'event_broadcast should be called');
st.equal(broadcastArgs.BROADCAST_OPTION.id, 'existing-id', 'Should use existing variable ID');
st.equal(broadcastArgs.BROADCAST_OPTION.name, 'Existing Message', 'Should use canonical name');
st.equal(Object.keys(stage.variables).length, 1, 'Should not create duplicate variable');

st.end();
});

t.tearDown(() => {
BlockUtility.lastInstance = originalLastInstance;
});

t.end();
});
8 changes: 7 additions & 1 deletion test/unit/mesh_service_v2_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ const createMockBlocks = broadcastCallback => ({
sequencer: {},
emit: () => {},
on: () => {},
off: () => {}
off: () => {},
getTargetForStage: () => ({
lookupBroadcastMsg: (id, name) => ({id: `id-${name}`, name: name}),
lookupBroadcastByInputValue: name => ({id: `id-${name}`, name: name}),
createVariable: () => {}
}),
requestBlocksUpdate: () => {}
},
opcodeFunctions: {
event_broadcast: args => {
Expand Down
Loading