@ringozz/react-godot 4.7.2-604 → 4.7.2-614

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.
package/README.md CHANGED
@@ -90,6 +90,19 @@ If you already have a pre-existing Godot `Node`, `Resource`, or `PackedScene` in
90
90
  - **A `PackedScene` `object` is instantiated once per host mount** — each element gets a fresh node tree; the result is a plain `Node` (not `.reference()`'d) freed on unmount. The same `PackedScene` can feed multiple elements; give same-type siblings stable `key`s.
91
91
  - **Dynamic same-type sibling lists need stable `key`s.** Godot host nodes are identity, not content: React reconciles keyless siblings by position, so deleting the *first* of two same-type siblings reuses its Godot node for the survivor. A plain state re-render does this too, and Bun's web dev server makes it especially likely — its React Fast Refresh re-renders the edited component **in place** (no app re-run), reusing the same fibers and Godot nodes, so a deleted sibling's node can survive on its neighbor and the neighbor's node is freed. Add `key="..."` to each dynamically toggled same-type sibling to keep nodes pinned to the right elements.
92
92
 
93
+ ### 1b. Attaching a Script with `script`
94
+
95
+ Use the `script` prop to attach a `Script` resource (e.g. an imported GDScript `.gd` module) to a node on creation. The script is attached **before the node enters the tree**, so Godot fires `_ready` and auto-enables the script's `_process`/`_input` — no manual `set_process(true)`/`set_process_input(true)` calls needed:
96
+
97
+ ```tsx
98
+ import controller from './camera_controller.gd';
99
+
100
+ <Camera3D script={use(controller)} current={true} fov={55} />
101
+ ```
102
+
103
+ - **Attach before tree entry means `_ready`/`_process`/`_input` work.** Attaching to an already-in-tree node (e.g. in a `useEffect`) never fires `_ready` and leaves `_process`/`_input` disabled — prefer `script` over an effect for scripted nodes.
104
+ - **`script` is immutable on a mounted instance.** A re-render that changes `script` on an existing element throws — remount with a new `key` to swap the compiled script.
105
+
93
106
  ### 2. Resource Nesting with `attach`
94
107
 
95
108
  In Godot, nodes often hold references to `Resource` objects (such as shapes, meshes, materials, or environments). Use the `attach` prop to automatically assign a child resource to a specific property on its parent:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ringozz/react-godot",
3
3
  "author": "Vladimir Davidovich",
4
- "version": "4.7.2-604",
4
+ "version": "4.7.2-614",
5
5
  "description": "A React renderer for Godot Engine via @ringozz/godot",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -23,7 +23,7 @@
23
23
  "*.md"
24
24
  ],
25
25
  "dependencies": {
26
- "@ringozz/godot": "^4.7.2-604",
26
+ "@ringozz/godot": "^4.7.2-614",
27
27
  "@types/react-reconciler": "^0.33.0",
28
28
  "react-reconciler": "^0.33.0"
29
29
  },
@@ -33,7 +33,7 @@ const EMPTY = Object.freeze({});
33
33
  const attachments = new WeakMap<Instance, string>();
34
34
 
35
35
  export function createInstance<T extends Instance>(type: Type, props: Props, root: Container, hostContext: HostContext, internalHandle: Reconciler.OpaqueHandle): T {
36
- let { attach, object, ...rest } = props;
36
+ let { attach, object, script, ...rest } = props;
37
37
  if (object instanceof PackedScene)
38
38
  object = object.instantiate();
39
39
  else if (object instanceof RefCounted)
@@ -43,6 +43,9 @@ export function createInstance<T extends Instance>(type: Type, props: Props, roo
43
43
  if (attach)
44
44
  attachments.set(instance, attach);
45
45
 
46
+ if (script)
47
+ instance.setScript(script);
48
+
46
49
  commitUpdate(instance, type, EMPTY, rest, internalHandle);
47
50
  return instance;
48
51
  }
@@ -271,12 +274,14 @@ function isEqual(a: unknown, b: unknown) {
271
274
  }
272
275
 
273
276
  export function commitUpdate(instance: Instance, type: Type, prevProps: Props, nextProps: Props, internalHandle: Reconciler.OpaqueHandle): void {
274
- const { attach: attachOld, object: objectOld, ref: refOld, children: childrenOld, ...restOld } = prevProps;
275
- const { attach: attachNew, object: objectNew, ref: refNew, children: childrenNew, ...restNew } = nextProps;
277
+ const { attach: attachOld, object: objectOld, script: scriptOld, ref: refOld, children: childrenOld, ...restOld } = prevProps;
278
+ const { attach: attachNew, object: objectNew, script: scriptNew, ref: refNew, children: childrenNew, ...restNew } = nextProps;
276
279
  if (attachOld !== attachNew)
277
280
  throw new Error(`Cannot change attachment ${attachOld} to ${attachNew}`);
278
281
  if (objectOld !== objectNew)
279
282
  throw new Error(`Cannot change the \`object\` of a mounted instance (${objectOld} -> ${objectNew}); remount with a new key`);
283
+ if (scriptOld !== scriptNew)
284
+ throw new Error(`Cannot change the \`script\` of a mounted instance (${scriptOld} -> ${scriptNew}); remount with a new key`);
280
285
 
281
286
  const toText = (c: unknown) => Array.isArray(c) ? c.join('') : c as string;
282
287
  if (shouldSetTextContent(type, prevProps)) restOld['text'] = toText(childrenOld);
@@ -6,6 +6,7 @@ import type React from 'react';
6
6
  import type { Object } from '@ringozz/godot/Object';
7
7
  import type { Signal, GodotDictionary } from '@ringozz/godot';
8
8
  import type { PackedScene } from '@ringozz/godot/PackedScene';
9
+ import type { Script } from '@ringozz/godot/Script';
9
10
 
10
11
  // Identity check sensitive to readonly (deferred-conditional trick; the
11
12
  // comparison lives in return-type position, so it is independent of
@@ -24,6 +25,14 @@ export type InstanceProps<T extends Instance = Instance> = {
24
25
  object?: T | PackedScene;
25
26
  /** Attaches the element to a named property of the parent instead of adding it as a child. */
26
27
  attach?: string;
28
+ /** Attaches a Script (e.g. a GDScript) to the instance on creation, via
29
+ * `Object.setScript` *before* the node enters the tree. Attaching pre-tree —
30
+ * as opposed to in a mount effect on an already-in-tree node — lets Godot's
31
+ * NOTIFICATION_READY auto-enable the script's `_process`/`_input` (an
32
+ * already-in-tree node never fires `_ready`, so those callbacks stay
33
+ * disabled and must be enabled manually). Script is a method-only member of
34
+ * `Object` (not a settable property), so it gets dedicated handling here. */
35
+ script?: Script;
27
36
  };
28
37
 
29
38
  type ReactProps<P> = React.PropsWithChildren<React.RefAttributes<P>>;