onejs-react 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/host-config.ts +35 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onejs-react",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "React 19 renderer for OneJS (Unity UI Toolkit)",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -12,6 +12,13 @@ declare function clearTimeout(id: number): void;
12
12
 
13
13
  declare const console: { log: (...args: unknown[]) => void; error: (...args: unknown[]) => void };
14
14
 
15
+ // Native delegate callback helpers (from QuickJSBootstrap __csHelpers)
16
+ declare const __csHelpers: {
17
+ createDelegateCallback(fn: Function): number;
18
+ freeDelegateCallback(handle: number): void;
19
+ [key: string]: unknown;
20
+ };
21
+
15
22
  // Priority constants from react-reconciler/constants
16
23
  // These match React's internal lane priorities
17
24
  const DiscreteEventPriority = 2;
@@ -149,6 +156,8 @@ export interface Instance {
149
156
  hasMixedContent?: boolean;
150
157
  // For vector drawing: track the current generateVisualContent callback
151
158
  visualContentCallback?: GenerateVisualContentCallback;
159
+ // Native callback handle for the above (used to free slot on replacement)
160
+ visualContentCallbackHandle?: number;
152
161
  }
153
162
 
154
163
  export type TextInstance = Instance; // For Label elements with text content
@@ -435,6 +444,15 @@ function untrackParent(child: CSObject) {
435
444
  }
436
445
  }
437
446
 
447
+ // Free native callback handles tracked on an instance (prevents callback table leak)
448
+ function cleanupCallbackHandles(instance: Instance) {
449
+ if (instance.visualContentCallbackHandle !== undefined) {
450
+ __csHelpers.freeDelegateCallback(instance.visualContentCallbackHandle);
451
+ instance.visualContentCallbackHandle = undefined;
452
+ instance.visualContentCallback = undefined;
453
+ }
454
+ }
455
+
438
456
  // Apply event handlers
439
457
  function applyEvents(instance: Instance, props: BaseProps) {
440
458
  for (const [propName, eventType] of Object.entries(EVENT_PROPS)) {
@@ -468,17 +486,27 @@ function applyVisualContentCallback(instance: Instance, props: BaseProps) {
468
486
  if (callback !== existingCallback) {
469
487
  const element = instance.element as unknown as { generateVisualContent: GenerateVisualContentCallback | null };
470
488
 
471
- // Remove old callback if exists
489
+ // Free old native callback handle to prevent callback table leak
472
490
  if (existingCallback) {
473
- // Clear the delegate via C# interop
474
491
  element.generateVisualContent = null;
492
+ if (instance.visualContentCallbackHandle !== undefined) {
493
+ __csHelpers.freeDelegateCallback(instance.visualContentCallbackHandle);
494
+ }
495
+ instance.visualContentCallbackHandle = undefined;
475
496
  }
476
497
 
477
498
  // Add new callback if provided
478
499
  if (callback) {
479
- // Assign callback to generateVisualContent property
480
- // The C# interop layer handles the delegate conversion
481
- element.generateVisualContent = callback;
500
+ // Register with argument wrapping and track the handle for cleanup
501
+ const handle = __csHelpers.createDelegateCallback(callback);
502
+ if (handle >= 0) {
503
+ instance.visualContentCallbackHandle = handle;
504
+ // Pass pre-resolved handle directly — bypasses __resolveValue's auto-registration
505
+ element.generateVisualContent = { __csCallbackHandle: handle } as any;
506
+ } else {
507
+ // WebGL path: no native callback table
508
+ element.generateVisualContent = callback;
509
+ }
482
510
  instance.visualContentCallback = callback;
483
511
  } else {
484
512
  instance.visualContentCallback = undefined;
@@ -947,6 +975,7 @@ export const hostConfig = {
947
975
  removeMergedTextChild(parentInstance, child);
948
976
  } else {
949
977
  __eventAPI.removeAllEventListeners(child.element);
978
+ cleanupCallbackHandles(child);
950
979
  parentInstance.element.Remove(child.element);
951
980
  }
952
981
  untrackParent(child.element);
@@ -954,6 +983,7 @@ export const hostConfig = {
954
983
 
955
984
  removeChildFromContainer(container: Container, child: Instance) {
956
985
  __eventAPI.removeAllEventListeners(child.element);
986
+ cleanupCallbackHandles(child);
957
987
  container.Remove(child.element);
958
988
  untrackParent(child.element);
959
989
  },