onejs-react 0.1.21 → 0.1.22

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 +49 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onejs-react",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "React 19 renderer for OneJS (Unity UI Toolkit)",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -71,6 +71,10 @@ declare const CS: {
71
71
  };
72
72
  FrostedGlassElement: new () => CSObject;
73
73
  };
74
+ StyleBridge: {
75
+ ApplyStyles: (element: CSObject, styles: Record<string, unknown>) => void;
76
+ AddClassesBatch: (element: CSObject, classes: string[]) => void;
77
+ };
74
78
  };
75
79
  };
76
80
 
@@ -385,47 +389,73 @@ function getRenderTextureHandle(value: RenderTextureRef): number {
385
389
  return value.__rtHandle ?? value.__handle ?? -1;
386
390
  }
387
391
 
388
- // Apply style properties to element, returns the set of applied keys
392
+ // Apply style properties to element, returns the set of applied keys.
393
+ //
394
+ // Batched path: parsed style values are collected into a single dict and sent
395
+ // to CS.OneJS.StyleBridge.ApplyStyles in one __cs.invoke crossing instead of
396
+ // one per property. On WebGL each crossing is ~3ms (JSON marshal + reflection),
397
+ // so the difference is ~N invokes vs 1 invoke per element. backgroundImage
398
+ // stays on its individual GPU-bridge path since it's not a plain IStyle setter.
389
399
  function applyStyle(element: CSObject, style: ViewStyle | undefined): Set<string> {
390
400
  const appliedKeys = new Set<string>();
391
401
  if (!style) return appliedKeys;
392
402
 
393
- const s = element.style;
403
+ const batched: Record<string, unknown> = {}
404
+
394
405
  for (const [key, value] of Object.entries(style)) {
395
406
  if (value === undefined) continue;
396
407
 
397
- // Handle shorthand properties
398
408
  const expanded = STYLE_SHORTHANDS[key];
399
409
  if (expanded) {
400
- // Parse the value once, apply to all expanded properties
401
- const parsed = parseStyleValue(expanded[0], value);
410
+ const parsed = resolveForBatch(parseStyleValue(expanded[0], value));
402
411
  for (const prop of expanded) {
403
- s[prop] = parsed;
412
+ batched[prop] = parsed;
404
413
  appliedKeys.add(prop);
405
414
  }
406
415
  } else if (key === "backgroundImage") {
407
416
  if (value == null) {
408
417
  CS.OneJS.GPU.GPUBridge.ClearElementBackgroundImage(element);
409
418
  } else if (isRenderTextureHandle(value)) {
410
- // GPU compute RenderTexture handles (via rt.getUnityObject())
411
419
  const handle = getRenderTextureHandle(value);
412
420
  if (handle >= 0) {
413
421
  CS.OneJS.GPU.GPUBridge.SetElementBackgroundImage(element, handle);
414
422
  }
415
423
  } else if (typeof value === "object" && "__csHandle" in value) {
416
- // C# objects: Texture2D, Sprite, VectorImage, RenderTexture
417
424
  CS.OneJS.GPU.GPUBridge.SetElementBackgroundFromObject(element, value);
418
425
  }
419
426
  appliedKeys.add(key);
420
427
  } else {
421
- // Parse and apply individual property
422
- s[key] = parseStyleValue(key, value);
428
+ batched[key] = resolveForBatch(parseStyleValue(key, value));
423
429
  appliedKeys.add(key);
424
430
  }
425
431
  }
432
+
433
+ if (Object.keys(batched).length > 0) {
434
+ CS.OneJS.StyleBridge.ApplyStyles(element, batched);
435
+ }
436
+
426
437
  return appliedKeys;
427
438
  }
428
439
 
440
+ // Force-resolve CS path proxies (e.g. CS.UnityEngine.UIElements.Justify.Center)
441
+ // to their underlying int value. parseEnumValue and parseLength's StyleKeyword
442
+ // cases return path proxies whose .valueOf() reads the int via GetField. The
443
+ // non-batched __cs.invoke path resolved these implicitly via __resolveValue;
444
+ // the batched path JSON.stringifies the whole dict, so path proxies serialize
445
+ // via toJSON to {__csTypeRef:...} which C# can't interpret as an enum value.
446
+ // CS object proxies (with __csHandle) keep their toJSON shape — only path
447
+ // proxies need coercion.
448
+ function resolveForBatch(value: unknown): unknown {
449
+ // Path proxies (e.g. CS.UnityEngine.UIElements.Justify.Center) have a
450
+ // function as their underlying Proxy target so they can also be invoked
451
+ // as constructors — so typeof is "function", not "object". Just check the
452
+ // __csPathProxy sentinel and rely on truthy/property semantics.
453
+ if (value && (value as any).__csPathProxy) {
454
+ return Number(value)
455
+ }
456
+ return value
457
+ }
458
+
429
459
  // Clear style properties that are no longer in the new style
430
460
  function clearRemovedStyles(element: CSObject, oldKeys: Set<string>, newKeys: Set<string>) {
431
461
  const s = element.style;
@@ -458,13 +488,18 @@ function parseClassNames(className: string | undefined): Set<string> {
458
488
  return result;
459
489
  }
460
490
 
461
- // Apply className(s) to element (with escaping for Tailwind/USS compatibility)
491
+ // Apply className(s) to element (with escaping for Tailwind/USS compatibility).
492
+ // Routes through StyleBridge.AddClassesBatch so a multi-class string is one
493
+ // __cs.invoke crossing instead of one per class.
462
494
  function applyClassName(element: CSObject, className: string | undefined) {
463
495
  if (!className) return;
464
496
 
465
- const classes = className.split(/\s+/).filter(Boolean);
466
- for (const cls of classes) {
467
- element.AddToClassList(escapeClassName(cls));
497
+ const classes: string[] = [];
498
+ for (const cls of className.split(/\s+/)) {
499
+ if (cls) classes.push(escapeClassName(cls));
500
+ }
501
+ if (classes.length > 0) {
502
+ CS.OneJS.StyleBridge.AddClassesBatch(element, classes);
468
503
  }
469
504
  }
470
505