From 2b813f6bb788c2c12443cf378bb2eb5380a2af99 Mon Sep 17 00:00:00 2001 From: Tchips46 Date: Sun, 7 Jun 2026 19:06:54 +0900 Subject: [PATCH 01/10] feat(core-editor): stop game event, hot reload only needed component param --- .../src/common/context/event-emitter.type.ts | 53 +++++++++++-------- .../src/common/context/events/core-events.ts | 23 ++++++++ .../common/context/events/editor-events.ts | 9 ++++ .../src/common/context/options.type.ts | 10 ++-- packages/core-editor/src/core/core.ts | 9 ++-- .../core-editor/src/editor/core-editor.ts | 33 ++++++++---- .../core-editor/test/editor-feature.spec.ts | 4 +- packages/ecs-lib/wasm/Registry.hpp | 2 +- 8 files changed, 99 insertions(+), 44 deletions(-) create mode 100644 packages/core-editor/src/common/context/events/core-events.ts create mode 100644 packages/core-editor/src/common/context/events/editor-events.ts diff --git a/packages/core-editor/src/common/context/event-emitter.type.ts b/packages/core-editor/src/common/context/event-emitter.type.ts index 790bddd1..15cb504a 100644 --- a/packages/core-editor/src/common/context/event-emitter.type.ts +++ b/packages/core-editor/src/common/context/event-emitter.type.ts @@ -1,15 +1,15 @@ -/** - * Events that the NanoForge editor can emit to the running engine. - */ -export enum EventTypeEnum { - /** Reload modules without restarting the engine (live-patch). */ - HOT_RELOAD = "hot-reload", - /** Fully restart the engine with the latest changes. */ - HARD_RELOAD = "hard-reload", -} - /** Callback signature for event listeners. */ -export type ListenerType = (...args: any[]) => void; +export type ListenerType< + Events extends string, + EventsMap extends Record, + K extends keyof EventsMap, +> = (...args: EventsMap[K]) => void; + +/** Signature of the waiting for execution events. */ +export type QueuedEvent = { + event: K; + args: EventsMap[K]; +}; /** * Simple event emitter interface used for communication between the NanoForge @@ -19,14 +19,17 @@ export type ListenerType = (...args: any[]) => void; * Listeners are queued and processed via `runEvents` to keep the engine * loop deterministic. */ -export interface IEventEmitter { +export interface IEventEmitter> { /** Map of event names to their registered listeners. */ - listeners: Record; + listeners: { + [K in keyof EventsMap]?: ListenerType[]; + }; + /** Queue of events waiting to be dispatched by `runEvents`. */ - eventQueue: { event: EventTypeEnum | string; args: any[] }[]; + eventQueue: QueuedEvent[]; /** Drain the event queue and invoke all matching listeners. */ - runEvents: () => void; + runEvents(): void; /** * Enqueue an event for dispatching on the next `runEvents` call. @@ -34,7 +37,7 @@ export interface IEventEmitter { * @param event - Event name or EventTypeEnum value. * @param args - Optional arguments forwarded to listeners. */ - emitEvent: (event: EventTypeEnum, ...args: any) => void; + emitEvent(event: K, ...args: EventsMap[K]): void; /** * Register a listener for an event. Alias: `on`. @@ -42,9 +45,12 @@ export interface IEventEmitter { * @param event - Event name to subscribe to. * @param listener - Callback invoked when the event fires. */ - addListener: (event: EventTypeEnum | string, listener: ListenerType) => void; + addListener( + event: K, + listener: ListenerType, + ): void; /** Alias for `addListener`. */ - on: (event: EventTypeEnum | string, listener: ListenerType) => void; + on(event: K, listener: ListenerType): void; /** * Remove a previously registered listener. Alias: `off`. @@ -52,16 +58,19 @@ export interface IEventEmitter { * @param event - Event name to unsubscribe from. * @param listener - The exact listener function to remove. */ - removeListener: (event: EventTypeEnum | string, listener: ListenerType) => void; + removeListener( + event: K, + listener: ListenerType, + ): void; /** Alias for `removeListener`. */ - off: (event: EventTypeEnum | string, listener: ListenerType) => void; + off(event: K, listener: ListenerType): void; /** * Remove all listeners registered for a specific event. * * @param event - Event name whose listeners should be cleared. */ - removeListenersForEvent: (event: EventTypeEnum | string) => void; + removeListenersForEvent(event: keyof EventsMap): void; /** Remove every registered listener across all events. */ - removeAllListeners: () => void; + removeAllListeners(): void; } diff --git a/packages/core-editor/src/common/context/events/core-events.ts b/packages/core-editor/src/common/context/events/core-events.ts new file mode 100644 index 00000000..0556c0c4 --- /dev/null +++ b/packages/core-editor/src/common/context/events/core-events.ts @@ -0,0 +1,23 @@ +/** + * Events that the NanoForge editor can emit to the running engine. + */ +export enum CoreEvents { + /** Reload only changed entity components params (live-patch) */ + HOT_RELOAD = "hot-reload", + /** Reload all entities component params */ + HARD_RELOAD = "hard-reload", + /** Don't execute the run function for n ms or until UNPAUSE_GAME */ + PAUSE_GAME = "pause-game", + /** End main loop and clear */ + STOP_GAME = "stop-game", + /** resume executing the run function */ + UNPAUSE_GAME = "unpause-game", +} + +export interface CoreEventsMap { + [CoreEvents.HOT_RELOAD]: []; + [CoreEvents.HARD_RELOAD]: []; + [CoreEvents.PAUSE_GAME]: [duration: number]; + [CoreEvents.STOP_GAME]: []; + [CoreEvents.UNPAUSE_GAME]: []; +} diff --git a/packages/core-editor/src/common/context/events/editor-events.ts b/packages/core-editor/src/common/context/events/editor-events.ts new file mode 100644 index 00000000..f340b43c --- /dev/null +++ b/packages/core-editor/src/common/context/events/editor-events.ts @@ -0,0 +1,9 @@ +// ! Please do not remove this event unless a new one replaces it, it causes types issues + +export enum EditorEvents { + EMPTY = "empty", +} + +export interface EditorEventsMap { + [EditorEvents.EMPTY]: []; +} diff --git a/packages/core-editor/src/common/context/options.type.ts b/packages/core-editor/src/common/context/options.type.ts index d8a09fdf..c2bd724f 100644 --- a/packages/core-editor/src/common/context/options.type.ts +++ b/packages/core-editor/src/common/context/options.type.ts @@ -1,4 +1,6 @@ import { type IEventEmitter } from "./event-emitter.type"; +import { type CoreEvents, type CoreEventsMap } from "./events/core-events"; +import { type EditorEvents, type EditorEventsMap } from "./events/editor-events"; import { type Save } from "./save.type"; /** @@ -25,9 +27,9 @@ export interface IEditorRunClientOptions { /** Serialised scene state loaded or saved by the editor. */ save: Save; /** Event emitter for core-to-editor communication. */ - coreEvents: IEventEmitter; + coreEvents: IEventEmitter; /** Event emitter for editor-to-core communication. */ - editorEvents: IEventEmitter; + editorEvents: IEventEmitter; }; } @@ -47,8 +49,8 @@ export interface IEditorRunServerOptions { /** Serialised scene state loaded or saved by the editor. */ save: Save; /** Event emitter for core-to-editor communication. */ - coreEvents: IEventEmitter; + coreEvents: IEventEmitter; /** Event emitter for editor-to-core communication. */ - editorEvents: IEventEmitter; + editorEvents: IEventEmitter; }; } diff --git a/packages/core-editor/src/core/core.ts b/packages/core-editor/src/core/core.ts index b917ce2e..46579555 100644 --- a/packages/core-editor/src/core/core.ts +++ b/packages/core-editor/src/core/core.ts @@ -36,6 +36,7 @@ export class Core { this._configRegistry = new ConfigRegistry(options.env); await this.runInit(this.getInitContext(options)); this.editor = new CoreEditor( + this, options.editor, this.config.getComponentSystemLibrary().library, ); @@ -72,16 +73,16 @@ export class Core { setTimeout(render); } + public getExecutionContext(): EditableExecutionContext { + return new EditableExecutionContext(this.context, this.config.libraryManager); + } + private getInitContext(options: IEditorRunOptions): InitContext { if (!this._configRegistry) throw new NfNotInitializedException("Core"); return new InitContext(this.context, this.config.libraryManager, this._configRegistry, options); } - private getExecutionContext(): EditableExecutionContext { - return new EditableExecutionContext(this.context, this.config.libraryManager); - } - private getClearContext(): ClearContext { return new ClearContext(this.context, this.config.libraryManager); } diff --git a/packages/core-editor/src/editor/core-editor.ts b/packages/core-editor/src/editor/core-editor.ts index f9bb7494..3cbd7aff 100644 --- a/packages/core-editor/src/editor/core-editor.ts +++ b/packages/core-editor/src/editor/core-editor.ts @@ -1,33 +1,36 @@ import { NfNotFound } from "@nanoforge-dev/common"; import { type ECSClientLibrary, type Entity } from "@nanoforge-dev/ecs-client"; -import { EventTypeEnum } from "../common/context/event-emitter.type"; +import { CoreEvents } from "../common/context/events/core-events"; import { type IEditorRunOptions } from "../common/context/options.type"; +import { type Save } from "../common/context/save.type"; +import { type Core } from "../core/core"; export class CoreEditor { private editor: IEditorRunOptions["editor"]; private ecsLibrary: ECSClientLibrary; - constructor(editor: IEditorRunOptions["editor"], ecsLibrary: ECSClientLibrary) { + private lastLoadedSave: Save; + private core: Core; + + constructor(core: Core, editor: IEditorRunOptions["editor"], ecsLibrary: ECSClientLibrary) { this.editor = editor; + this.lastLoadedSave = JSON.parse(JSON.stringify(this.editor.save)); this.ecsLibrary = ecsLibrary; - this.editor.coreEvents?.addListener( - EventTypeEnum.HOT_RELOAD, - this.askEntitiesHotReload.bind(this), - ); + this.editor.coreEvents?.addListener(CoreEvents.HOT_RELOAD, this.hotReloadEvent.bind(this)); + this.editor.coreEvents?.addListener(CoreEvents.STOP_GAME, this.stopGameEvent.bind(this)); + this.core = core; } public runEvents() { this.editor.coreEvents?.runEvents(); } - public askEntitiesHotReload(): void { + public hotReloadEvent(): void { const reg = this.ecsLibrary.registry; const save = this.editor.save; save.entities.forEach(({ id, components }) => { Object.entries(components).forEach(([componentName, params]) => { - const ogComponent = save.components.find(({ name: paramName }) => { - return paramName == componentName; - }); + const ogComponent = save.components.find(({ name }) => name === componentName); if (!ogComponent) { throw new NfNotFound("Component: " + componentName + " not found in saved components"); } @@ -36,11 +39,19 @@ export class CoreEditor { name: componentName, }); Object.entries(params).forEach(([paramName, paramValue]) => { - ecsComponent[paramName] = paramValue; + const lastLoadedParam = this.lastLoadedSave.entities.find((e) => e.id === id)?.components[ + componentName + ]?.[paramName]; + if (lastLoadedParam !== paramValue) ecsComponent[paramName] = paramValue; }); reg.addComponent(ecsEntity, ecsComponent); }); }); + this.lastLoadedSave = JSON.parse(JSON.stringify(save)); + } + + public stopGameEvent(): void { + this.core.getExecutionContext().application.setIsRunning(false); } private getEntityFromEntityId(entityId: string): Entity { diff --git a/packages/core-editor/test/editor-feature.spec.ts b/packages/core-editor/test/editor-feature.spec.ts index ead57efb..d39c872a 100644 --- a/packages/core-editor/test/editor-feature.spec.ts +++ b/packages/core-editor/test/editor-feature.spec.ts @@ -18,7 +18,7 @@ describe("EditorFeatures", () => { events.emitEvent(EventTypeEnum.HOT_RELOAD); events.emitEvent(EventTypeEnum.HOT_RELOAD); const spyHotReload = vi - .spyOn(CoreEditor.prototype, "askEntitiesHotReload") + .spyOn(CoreEditor.prototype, "hotReloadEvent") .mockImplementation(() => {}); new CoreEditor( { coreEvents: events } as IEditorRunOptions["editor"], @@ -120,7 +120,7 @@ describe("EditorFeatures", () => { } as any as Save, } as any as IEditorRunOptions["editor"], { registry: fakeReg } as any as ECSClientLibrary, - ).askEntitiesHotReload(); + ).hotReloadEvent(); expect(fakeReg.getComponents).toHaveBeenCalledWith({ name: "__RESERVED_ENTITY_ID" }); expect(getIndex).toHaveBeenNthCalledWith(1, { entityId: "ent2", diff --git a/packages/ecs-lib/wasm/Registry.hpp b/packages/ecs-lib/wasm/Registry.hpp index eda9d55f..4cde1ee0 100644 --- a/packages/ecs-lib/wasm/Registry.hpp +++ b/packages/ecs-lib/wasm/Registry.hpp @@ -45,7 +45,7 @@ namespace nfo { SparseArray ®ister_component(const Component &component) { std::string component_type(get_js_class_name(component)); - if (component_type == "entity" || component_type == "id") + if (component_type == "entity" || component_type == "id" || component_type == UNKNOWN_COMPONENT_TYPE) throw std::runtime_error("Component type '" + component_type + "' not supported : you can't use : id, entity, " + UNKNOWN_COMPONENT_TYPE); if (!_components_arrays.contains(component_type)) _components_arrays.emplace(component_type, SparseArray()); From 74dd33ce97918ceaa563d65c62ae8ea9a7174d6c Mon Sep 17 00:00:00 2001 From: Tchips46 Date: Sun, 7 Jun 2026 19:10:50 +0900 Subject: [PATCH 02/10] feat(core-editor): stop game event, hot reload only needed component param --- packages/core-editor/test/editor-feature.spec.ts | 9 ++++++--- packages/ecs-lib/lib/libecs.d.ts | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/core-editor/test/editor-feature.spec.ts b/packages/core-editor/test/editor-feature.spec.ts index d39c872a..b5028417 100644 --- a/packages/core-editor/test/editor-feature.spec.ts +++ b/packages/core-editor/test/editor-feature.spec.ts @@ -1,9 +1,10 @@ import { type ECSClientLibrary } from "@nanoforge-dev/ecs-client"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { EventTypeEnum } from "../src/common/context/event-emitter.type"; +import { CoreEvents } from "../src/common/context/events/core-events"; import type { IEditorRunOptions } from "../src/common/context/options.type"; import { type Save, type SaveComponent, type SaveEntity } from "../src/common/context/save.type"; +import { type Core } from "../src/core/core"; import { CoreEditor } from "../src/editor/core-editor"; import { EventEmitter } from "./helpers/event-emitter"; @@ -15,12 +16,13 @@ describe("EditorFeatures", () => { describe("eventEmitter", () => { it("should execute eventQueue once", async () => { const events = new EventEmitter(); - events.emitEvent(EventTypeEnum.HOT_RELOAD); - events.emitEvent(EventTypeEnum.HOT_RELOAD); + events.emitEvent(CoreEvents.HOT_RELOAD); + events.emitEvent(CoreEvents.HOT_RELOAD); const spyHotReload = vi .spyOn(CoreEditor.prototype, "hotReloadEvent") .mockImplementation(() => {}); new CoreEditor( + {} as Core, { coreEvents: events } as IEditorRunOptions["editor"], {} as ECSClientLibrary, ).runEvents(); @@ -113,6 +115,7 @@ describe("EditorFeatures", () => { ]; const fakeReg = new FakeRegistry(); new CoreEditor( + {} as Core, { save: { components, diff --git a/packages/ecs-lib/lib/libecs.d.ts b/packages/ecs-lib/lib/libecs.d.ts index 6ff945f1..d4d8cf0b 100644 --- a/packages/ecs-lib/lib/libecs.d.ts +++ b/packages/ecs-lib/lib/libecs.d.ts @@ -16,7 +16,7 @@ export interface ClassHandle { [Symbol.dispose](): void; clone(): this; } -export interface container extends ClassHandle { +export interface container extends ClassHandle, Iterable { size(): number; get(_0: number): any | undefined | undefined; push_back(_0?: any): void; From 8e7092049f7952dda897315313e12aa8c08d8096 Mon Sep 17 00:00:00 2001 From: Tchips46 Date: Sun, 7 Jun 2026 19:14:51 +0900 Subject: [PATCH 03/10] feat(core-editor): stop game event, hot reload only needed component param --- packages/core-editor/test/editor-feature.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-editor/test/editor-feature.spec.ts b/packages/core-editor/test/editor-feature.spec.ts index b5028417..3931f1fe 100644 --- a/packages/core-editor/test/editor-feature.spec.ts +++ b/packages/core-editor/test/editor-feature.spec.ts @@ -22,7 +22,7 @@ describe("EditorFeatures", () => { .spyOn(CoreEditor.prototype, "hotReloadEvent") .mockImplementation(() => {}); new CoreEditor( - {} as Core, + { editor: { save: {} } } as unknown as Core, { coreEvents: events } as IEditorRunOptions["editor"], {} as ECSClientLibrary, ).runEvents(); @@ -115,7 +115,7 @@ describe("EditorFeatures", () => { ]; const fakeReg = new FakeRegistry(); new CoreEditor( - {} as Core, + { editor: { save: {} } } as unknown as Core, { save: { components, From 8a382aa57edb2b97de8853f34a9104aa3733b580 Mon Sep 17 00:00:00 2001 From: Tchips46 Date: Sun, 7 Jun 2026 19:32:51 +0900 Subject: [PATCH 04/10] feat(core-editor): stop game event, hot reload only needed component param --- packages/core-editor/test/editor-feature.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core-editor/test/editor-feature.spec.ts b/packages/core-editor/test/editor-feature.spec.ts index 3931f1fe..1a401964 100644 --- a/packages/core-editor/test/editor-feature.spec.ts +++ b/packages/core-editor/test/editor-feature.spec.ts @@ -22,8 +22,8 @@ describe("EditorFeatures", () => { .spyOn(CoreEditor.prototype, "hotReloadEvent") .mockImplementation(() => {}); new CoreEditor( - { editor: { save: {} } } as unknown as Core, - { coreEvents: events } as IEditorRunOptions["editor"], + {} as unknown as Core, + { coreEvents: events, save: { libraries: [] } } as unknown as IEditorRunOptions["editor"], {} as ECSClientLibrary, ).runEvents(); expect(spyHotReload).toHaveBeenCalledTimes(2); @@ -115,7 +115,7 @@ describe("EditorFeatures", () => { ]; const fakeReg = new FakeRegistry(); new CoreEditor( - { editor: { save: {} } } as unknown as Core, + {} as unknown as Core, { save: { components, @@ -140,13 +140,13 @@ describe("EditorFeatures", () => { expect(fakeReg.getEntityComponent).toHaveBeenNthCalledWith(1, 2, { name: "Position" }); expect(fakeReg.getEntityComponent).toHaveBeenNthCalledWith(2, 2, { name: "Bullets" }); expect(fakeReg.getEntityComponent).toHaveBeenNthCalledWith(3, 3, { name: "Position" }); - expect(fakeReg.addComponent).toHaveBeenNthCalledWith(1, 2, { name: "Position", x: 1, y: 2 }); + expect(fakeReg.addComponent).toHaveBeenNthCalledWith(1, 2, { name: "Position", x: 3, y: 4 }); expect(fakeReg.addComponent).toHaveBeenNthCalledWith(2, 2, { name: "Bullets", bulletTypes: ["fire", "water", "rocket"], - number: 1000, + number: 4, }); - expect(fakeReg.addComponent).toHaveBeenNthCalledWith(3, 3, { name: "Position", x: 5, y: 6 }); + expect(fakeReg.addComponent).toHaveBeenNthCalledWith(3, 3, { name: "Position", x: 7, y: 8 }); }); }); }); From a52a0438bc65319c21edfc213f6ce6669b3c5d14 Mon Sep 17 00:00:00 2001 From: Tchips46 Date: Tue, 9 Jun 2026 11:02:58 +0900 Subject: [PATCH 05/10] feat(core-editor): all events --- packages/core-editor/src/core/core.ts | 2 +- .../core-editor/src/editor/core-editor.ts | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/core-editor/src/core/core.ts b/packages/core-editor/src/core/core.ts index 46579555..4676e744 100644 --- a/packages/core-editor/src/core/core.ts +++ b/packages/core-editor/src/core/core.ts @@ -64,7 +64,7 @@ export class Core { return; } const tickStart = Date.now(); - await runner(tickStart - previousTick); + if (this.editor?.isPaused) await runner(tickStart - previousTick); previousTick = tickStart; setTimeout(render, tickLengthMs + tickStart - Date.now()); }; diff --git a/packages/core-editor/src/editor/core-editor.ts b/packages/core-editor/src/editor/core-editor.ts index 3cbd7aff..316a18c0 100644 --- a/packages/core-editor/src/editor/core-editor.ts +++ b/packages/core-editor/src/editor/core-editor.ts @@ -11,16 +11,24 @@ export class CoreEditor { private ecsLibrary: ECSClientLibrary; private lastLoadedSave: Save; private core: Core; + private _isPaused: boolean = false; constructor(core: Core, editor: IEditorRunOptions["editor"], ecsLibrary: ECSClientLibrary) { this.editor = editor; this.lastLoadedSave = JSON.parse(JSON.stringify(this.editor.save)); this.ecsLibrary = ecsLibrary; this.editor.coreEvents?.addListener(CoreEvents.HOT_RELOAD, this.hotReloadEvent.bind(this)); + this.editor.coreEvents?.addListener(CoreEvents.HARD_RELOAD, this.hardReloadEvent.bind(this)); + this.editor.coreEvents?.addListener(CoreEvents.PAUSE_GAME, this.pauseGameEvent.bind(this)); this.editor.coreEvents?.addListener(CoreEvents.STOP_GAME, this.stopGameEvent.bind(this)); + this.editor.coreEvents?.addListener(CoreEvents.UNPAUSE_GAME, this.unpauseGameEvent.bind(this)); this.core = core; } + get isPaused(): boolean { + return this._isPaused; + } + public runEvents() { this.editor.coreEvents?.runEvents(); } @@ -50,6 +58,35 @@ export class CoreEditor { this.lastLoadedSave = JSON.parse(JSON.stringify(save)); } + public hardReloadEvent(): void { + const reg = this.ecsLibrary.registry; + const save = this.editor.save; + save.entities.forEach(({ id, components }) => { + Object.entries(components).forEach(([componentName, params]) => { + const ogComponent = save.components.find(({ name }) => name === componentName); + if (!ogComponent) { + throw new NfNotFound("Component: " + componentName + " not found in saved components"); + } + const ecsEntity: Entity = this.getEntityFromEntityId(id); + const ecsComponent = reg.getEntityComponent(ecsEntity, { + name: componentName, + }); + Object.entries(params).forEach(([paramName, paramValue]) => { + ecsComponent[paramName] = paramValue; + }); + reg.addComponent(ecsEntity, ecsComponent); + }); + }); + this.lastLoadedSave = JSON.parse(JSON.stringify(save)); + } + + public pauseGameEvent(): void { + this._isPaused = true; + } + public unpauseGameEvent(): void { + this._isPaused = false; + } + public stopGameEvent(): void { this.core.getExecutionContext().application.setIsRunning(false); } From b50dad01782a152a7659881915fc2d67740f2bf0 Mon Sep 17 00:00:00 2001 From: Tchips46 Date: Tue, 9 Jun 2026 11:13:05 +0900 Subject: [PATCH 06/10] fix: merge --- packages/core-editor/src/common/context/save.type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-editor/src/common/context/save.type.ts b/packages/core-editor/src/common/context/save.type.ts index 2337b8bc..d29f2987 100644 --- a/packages/core-editor/src/common/context/save.type.ts +++ b/packages/core-editor/src/common/context/save.type.ts @@ -57,7 +57,7 @@ export interface SaveEntity { } /** - * Root serialised scene state exchanged between the editor and the engine. + * Root serialized scene state exchanged between the editor and the engine. */ export interface Save { /** All libraries registered in the scene. */ From c62a97b8ca2d517c988961db9c55ed2cfa2b155d Mon Sep 17 00:00:00 2001 From: Tchips46 Date: Tue, 9 Jun 2026 11:14:02 +0900 Subject: [PATCH 07/10] fix: merge --- packages/core-editor/src/common/context/events/core-events.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-editor/src/common/context/events/core-events.ts b/packages/core-editor/src/common/context/events/core-events.ts index 0556c0c4..eff8b746 100644 --- a/packages/core-editor/src/common/context/events/core-events.ts +++ b/packages/core-editor/src/common/context/events/core-events.ts @@ -6,7 +6,7 @@ export enum CoreEvents { HOT_RELOAD = "hot-reload", /** Reload all entities component params */ HARD_RELOAD = "hard-reload", - /** Don't execute the run function for n ms or until UNPAUSE_GAME */ + /** Don't execute the run function until UNPAUSE_GAME */ PAUSE_GAME = "pause-game", /** End main loop and clear */ STOP_GAME = "stop-game", @@ -17,7 +17,7 @@ export enum CoreEvents { export interface CoreEventsMap { [CoreEvents.HOT_RELOAD]: []; [CoreEvents.HARD_RELOAD]: []; - [CoreEvents.PAUSE_GAME]: [duration: number]; + [CoreEvents.PAUSE_GAME]: []; [CoreEvents.STOP_GAME]: []; [CoreEvents.UNPAUSE_GAME]: []; } From 72fd65e736a42c079cb81d87773b7f22b1540bb2 Mon Sep 17 00:00:00 2001 From: Tchips46 Date: Tue, 9 Jun 2026 15:56:43 +0900 Subject: [PATCH 08/10] feat: new save for hard reload --- .../core-editor/src/common/context/events/core-events.ts | 4 +++- packages/core-editor/src/editor/core-editor.ts | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/core-editor/src/common/context/events/core-events.ts b/packages/core-editor/src/common/context/events/core-events.ts index eff8b746..e27ed353 100644 --- a/packages/core-editor/src/common/context/events/core-events.ts +++ b/packages/core-editor/src/common/context/events/core-events.ts @@ -1,3 +1,5 @@ +import { type Save } from "../save.type"; + /** * Events that the NanoForge editor can emit to the running engine. */ @@ -16,7 +18,7 @@ export enum CoreEvents { export interface CoreEventsMap { [CoreEvents.HOT_RELOAD]: []; - [CoreEvents.HARD_RELOAD]: []; + [CoreEvents.HARD_RELOAD]: [save: Save]; [CoreEvents.PAUSE_GAME]: []; [CoreEvents.STOP_GAME]: []; [CoreEvents.UNPAUSE_GAME]: []; diff --git a/packages/core-editor/src/editor/core-editor.ts b/packages/core-editor/src/editor/core-editor.ts index 316a18c0..9d4b613a 100644 --- a/packages/core-editor/src/editor/core-editor.ts +++ b/packages/core-editor/src/editor/core-editor.ts @@ -58,9 +58,10 @@ export class CoreEditor { this.lastLoadedSave = JSON.parse(JSON.stringify(save)); } - public hardReloadEvent(): void { + public hardReloadEvent(save: Save): void { const reg = this.ecsLibrary.registry; - const save = this.editor.save; + this.editor.save = save; + this.lastLoadedSave = JSON.parse(JSON.stringify(save)); save.entities.forEach(({ id, components }) => { Object.entries(components).forEach(([componentName, params]) => { const ogComponent = save.components.find(({ name }) => name === componentName); @@ -77,7 +78,6 @@ export class CoreEditor { reg.addComponent(ecsEntity, ecsComponent); }); }); - this.lastLoadedSave = JSON.parse(JSON.stringify(save)); } public pauseGameEvent(): void { From d5438144cf4f0a194c4429b00423dc67a90bbfe4 Mon Sep 17 00:00:00 2001 From: Tchips46 Date: Tue, 9 Jun 2026 20:10:23 +0900 Subject: [PATCH 09/10] feat(core-editor): hot reload save param --- .../core-editor/src/common/context/events/core-events.ts | 2 +- packages/core-editor/src/editor/core-editor.ts | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/core-editor/src/common/context/events/core-events.ts b/packages/core-editor/src/common/context/events/core-events.ts index e27ed353..03d041f0 100644 --- a/packages/core-editor/src/common/context/events/core-events.ts +++ b/packages/core-editor/src/common/context/events/core-events.ts @@ -17,7 +17,7 @@ export enum CoreEvents { } export interface CoreEventsMap { - [CoreEvents.HOT_RELOAD]: []; + [CoreEvents.HOT_RELOAD]: [save: Save]; [CoreEvents.HARD_RELOAD]: [save: Save]; [CoreEvents.PAUSE_GAME]: []; [CoreEvents.STOP_GAME]: []; diff --git a/packages/core-editor/src/editor/core-editor.ts b/packages/core-editor/src/editor/core-editor.ts index 9d4b613a..40e52ade 100644 --- a/packages/core-editor/src/editor/core-editor.ts +++ b/packages/core-editor/src/editor/core-editor.ts @@ -33,9 +33,9 @@ export class CoreEditor { this.editor.coreEvents?.runEvents(); } - public hotReloadEvent(): void { + public hotReloadEvent(save: Save): void { const reg = this.ecsLibrary.registry; - const save = this.editor.save; + this.lastLoadedSave = JSON.parse(JSON.stringify(save)); save.entities.forEach(({ id, components }) => { Object.entries(components).forEach(([componentName, params]) => { const ogComponent = save.components.find(({ name }) => name === componentName); @@ -55,12 +55,10 @@ export class CoreEditor { reg.addComponent(ecsEntity, ecsComponent); }); }); - this.lastLoadedSave = JSON.parse(JSON.stringify(save)); } public hardReloadEvent(save: Save): void { const reg = this.ecsLibrary.registry; - this.editor.save = save; this.lastLoadedSave = JSON.parse(JSON.stringify(save)); save.entities.forEach(({ id, components }) => { Object.entries(components).forEach(([componentName, params]) => { From aae3d1f85c973b9e555e425169a57ca8d88abe91 Mon Sep 17 00:00:00 2001 From: Tchips46 Date: Tue, 9 Jun 2026 20:12:24 +0900 Subject: [PATCH 10/10] feat(core-editor): hot reload save param --- packages/core-editor/test/editor-feature.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-editor/test/editor-feature.spec.ts b/packages/core-editor/test/editor-feature.spec.ts index 1a401964..9dfc5a93 100644 --- a/packages/core-editor/test/editor-feature.spec.ts +++ b/packages/core-editor/test/editor-feature.spec.ts @@ -123,7 +123,7 @@ describe("EditorFeatures", () => { } as any as Save, } as any as IEditorRunOptions["editor"], { registry: fakeReg } as any as ECSClientLibrary, - ).hotReloadEvent(); + ).hotReloadEvent({ components, entities } as any as Save); expect(fakeReg.getComponents).toHaveBeenCalledWith({ name: "__RESERVED_ENTITY_ID" }); expect(getIndex).toHaveBeenNthCalledWith(1, { entityId: "ent2",