Skip to content
Merged
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/display/mixins/Cellsable.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const Cellsable = (superClass) => {
item.destroy({ children: true });
}
});
this.context.viewport.emit('object_transformed', this);
}
};
MixedClass.registerHandler(
Expand Down
2 changes: 1 addition & 1 deletion src/events/StateManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default class StateManager extends WildcardEventEmitter {
let instance = stateDef.instance;
if (!instance || !stateDef.isSingleton) {
const StateClass = stateDef.Class;
instance = new StateClass();
instance = new StateClass(name);
if (stateDef.isSingleton) {
stateDef.instance = instance;
}
Expand Down
12 changes: 7 additions & 5 deletions src/events/states/SelectionState.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ export default class SelectionState extends State {

/**
* Enters the selection state with a given context and configuration.
* @param {object} context - The application context, containing the viewport.
* @param {SelectionStateConfig} config - Configuration for the selection behavior.
* @param {...*} args - Additional arguments passed to the state.
*/
enter(context, config = {}) {
super.enter(context);
enter(...args) {
super.enter(...args);
const [_, config] = args;
this.config = deepMerge(defaultConfig, config);
this.viewport = this.context.viewport;
this.viewport.addChild(this._selectionBox);
Expand All @@ -103,7 +103,9 @@ export default class SelectionState extends State {
exit() {
super.exit();
this.#clear({ state: true, selectionBox: true, gesture: true });
this._selectionBox?.destroy(true);
if (this._selectionBox.parent) {
this._selectionBox.parent.removeChild(this._selectionBox);
}
Comment on lines +106 to +108

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Changing this._selectionBox?.destroy() to removeChild() introduces a memory leak for non-singleton states. When a non-singleton state instance is popped and discarded, the associated Graphics object for _selectionBox is never destroyed, and its WebGL resources are leaked. While not destroying is correct for reusable singleton states, the exit method needs to handle both cases.

A robust solution would involve a separate destroy lifecycle method. Given the current structure, a safer approach is to destroy _selectionBox here, and recreate it in enter() if it's been destroyed. This prevents leaks, though it has a minor performance cost for singletons.

    this._selectionBox?.destroy();

}

pause() {
Expand Down
7 changes: 5 additions & 2 deletions src/events/states/State.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ export default class State {
*/
abortController = new AbortController();

constructor() {
constructor(name) {
/**
* A reference to the shared context object provided by the StateManager.
* This context typically contains references to global objects like the viewport,
* the application instance, etc. It is null until `enter()` is called.
* @type {object | null}
*/
this.context = null;
this.key = name;
}

/**
Expand All @@ -48,9 +49,11 @@ export default class State {
* A new AbortController is created here for the state's lifecycle.
*
* @param {object} context - The shared application context from the StateManager.
* @param {...*} args - Additional arguments passed to the state.
*/
enter(context) {
enter(context, ...args) {
this.context = context;
this.args = args;
this.abortController = new AbortController();
}

Expand Down