HMR for blockly blocks

This commit is contained in:
azykov@mail.ru
2026-06-13 15:29:55 +03:00
parent 1ed3203ec5
commit a61b5f0105
36 changed files with 533 additions and 463 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ export const GameObjectView = observer(function (props: GameObjectViewProps) {
return <PlayerObjectView object={props.object as RuntimeGameObjectInstance} objectType={objectType} />;
function handleClick(): void {
state.game?.emitEvent('object_clicked', { object: props.object });
state.game?.emitEvent('objectClicked', { object: props.object });
}
return <ObjectViewInternal
@@ -1,4 +1,4 @@
import type { ObjectType, ObjectInstanceRigidBody, RuntimeGameObjectInstance, RuntimeObjectInstance } from "../types";
import type { ObjectType, RuntimeGameObjectInstance, RuntimeObjectInstance } from "../types";
import { useEffect, useImperativeHandle, useRef, type Ref } from "react";
import { Euler, Quaternion, type Group } from "three";
import { Instance, Instances } from "@react-three/drei";
@@ -94,9 +94,9 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props
return;
const otherObj = Object.values(game.scene.objects)
.find((o) => o.rb()?.handle === otherHandle);
game.emitEvent('object_touch_start', { object: gameObj, other: otherObj });
game.emitEvent('objectTouchStart', { object: gameObj, other: otherObj });
if (otherObj?.id === game.scene.playerObjectId)
game.emitEvent('player_touch_start', { object: gameObj, other: otherObj });
game.emitEvent('playerTouchStart', { object: gameObj, other: otherObj });
}}
onSync={(data) => {
runInAction(() => {
@@ -5,70 +5,11 @@ import * as Blockly from "blockly";
import type { ObjectType } from "../../types";
import './ScriptEditorView.scss';
import '../../blockly';
import { gameTheme, generateCode } from '../../blockly';
const TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = {
kind: 'categoryToolbox',
contents: [
{
kind: 'category',
name: 'Events',
colour: '180',
contents: [
{ kind: 'block', type: 'game_start_event' },
{ kind: 'block', type: 'object_touch_start_event' },
{ kind: 'block', type: 'player_touch_start_event' },
{ kind: 'block', type: 'object_clicked_event' },
],
},
{
kind: 'category',
name: 'Values',
colour: '260',
contents: [
{ kind: 'block', type: 'current_object_value' },
{ kind: 'block', type: 'current_object_type_value' },
{ kind: 'block', type: 'object_type_value' },
{ kind: 'block', type: 'any_object_of_type_value' },
{ kind: 'block', type: 'position_of_object_value' },
{ kind: 'block', type: 'pos_value' },
{ kind: 'block', type: 'dpos_value' },
{ kind: 'block', type: 'direction_to_position_value' },
{ kind: 'block', type: 'object_by_id_value' },
{ kind: 'block', type: 'player_object_value' },
],
},
{
kind: 'category',
name: 'Loops',
colour: '50',
contents: [
{ kind: 'block', type: 'loop_forever' },
],
},
{
kind: 'category',
name: 'Actions',
colour: '20',
contents: [
{ kind: 'block', type: 'console_log_action' },
{ kind: 'block', type: 'move_to_position_action' },
{ kind: 'block', type: 'move_in_direction_action' },
{ kind: 'block', type: 'physics_apply_impulse_action' },
{ kind: 'block', type: 'wait_seconds_action' },
],
},
{
kind: 'category',
name: 'Text',
colour: '160',
contents: [
{ kind: 'block', type: 'text' },
],
},
],
};
import '../../blockly/registrar';
import * as blocklyBarrel from '../../blockly';
import { gameTheme } from '../../blockly';
import { recompileAll } from '../../blockly/recompile';
import { state } from '../../state';
type ScriptEditorViewProps = {
objectType: ObjectType;
@@ -76,29 +17,44 @@ type ScriptEditorViewProps = {
export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorViewProps) {
const [popup, setPopup] = useState<{ title: string; content: string } | null>(null);
const [blocklyKey, setBlocklyKey] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);
const workspaceRef = useRef<Blockly.WorkspaceSvg | null>(null);
const objectTypeRef = useRef(objectType);
useLayoutEffect(() => { objectTypeRef.current = objectType; });
useEffect(() => {
if (!import.meta.hot)
return;
const handler = () => {
recompileAll(state.world.data.objectTypes);
setBlocklyKey(k => k + 1);
};
window.addEventListener('blockly-hmr', handler);
return () => window.removeEventListener('blockly-hmr', handler);
}, []);
useEffect(() => {
if (!containerRef.current)
return;
const workspace = Blockly.inject(containerRef.current, { toolbox: TOOLBOX, theme: gameTheme });
const toolbox = blocklyBarrel.barrelToToolbox(blocklyBarrel as Record<string, unknown>);
const workspace = Blockly.inject(containerRef.current, { toolbox, theme: gameTheme });
workspaceRef.current = workspace;
const listener = (e: Blockly.Events.Abstract) => {
if (e.isUiEvent)
return;
console.log('change event');
runInAction(() => {
const current = objectTypeRef.current;
const serialized = JSON.stringify(Blockly.serialization.workspaces.save(workspace));
current.program = serialized;
try {
current.javascript = generateCode(workspace);
current.javascript = blocklyBarrel.generateCode(workspace);
}
catch (err) {
console.log(err);
@@ -128,7 +84,9 @@ export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorV
workspace.dispose();
workspaceRef.current = null;
};
}, [objectType.program]);
// workspace should not reload when objectType.program changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [blocklyKey]);
useEffect(() => {
const workspace = workspaceRef.current;
@@ -145,7 +103,7 @@ export const ScriptEditorView = observer(function ({ objectType }: ScriptEditorV
} finally {
Blockly.Events.enable();
}
}, [objectType.id, objectType.program]);
}, [objectType.id]); // eslint-disable-line react-hooks/exhaustive-deps
const showProgram = () => {
const raw = objectType.program;