diff --git a/bun.lock b/bun.lock index fff04a6..3115e53 100644 --- a/bun.lock +++ b/bun.lock @@ -39,6 +39,7 @@ "version": "0.1.0", "dependencies": { "react-reconciler": "~0.32.0", + "type-fest": "^5.3.1", }, "devDependencies": { "@types/react-reconciler": "~0.32.0", @@ -320,6 +321,8 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "turbo": ["turbo@2.7.2", "", { "optionalDependencies": { "turbo-darwin-64": "2.7.2", "turbo-darwin-arm64": "2.7.2", "turbo-linux-64": "2.7.2", "turbo-linux-arm64": "2.7.2", "turbo-windows-64": "2.7.2", "turbo-windows-arm64": "2.7.2" }, "bin": { "turbo": "bin/turbo" } }, "sha512-5JIA5aYBAJSAhrhbyag1ZuMSgUZnHtI+Sq3H8D3an4fL8PeF+L1yYvbEJg47akP1PFfATMf5ehkqFnxfkmuwZQ=="], @@ -336,6 +339,8 @@ "turbo-windows-arm64": ["turbo-windows-arm64@2.7.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-tcnHvBhO515OheIFWdxA+qUvZzNqqcHbLVFc1+n+TJ1rrp8prYicQtbtmsiKgMvr/54jb9jOabU62URAobnB7g=="], + "type-fest": ["type-fest@5.3.1", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], diff --git a/packages/react-godot-renderer/package.json b/packages/react-godot-renderer/package.json index acef9d9..49221f2 100644 --- a/packages/react-godot-renderer/package.json +++ b/packages/react-godot-renderer/package.json @@ -41,7 +41,8 @@ "@types/react-reconciler": "~0.32.0" }, "dependencies": { - "react-reconciler": "~0.32.0" + "react-reconciler": "~0.32.0", + "type-fest": "^5.3.1" }, "peerDependencies": { "@types/react": "^19.2.0", diff --git a/packages/react-godot-renderer/src/Reconciler.ts b/packages/react-godot-renderer/src/Reconciler.ts index a148b33..ed00490 100644 --- a/packages/react-godot-renderer/src/Reconciler.ts +++ b/packages/react-godot-renderer/src/Reconciler.ts @@ -1,7 +1,9 @@ -import { Predicate, String } from "effect" -import { ClassDB, Node } from "godot" +import { ClassDB, Node, type NodePathMap } from "godot" import ReactReconciler from "react-reconciler" -import { DefaultEventPriority } from "react-reconciler/constants" +import { camelToSnake, hasProperty, snakeToPascal } from "./utils.js" + + +const DefaultEventPriority = 32 export const make = () => { @@ -11,13 +13,13 @@ export const make = () => { return ReactReconciler< string, Record, - Node, - Node, - Node, - Node, - Node, - Node, - Node, + Node, + Node, + Node, + Node, + Node, + Node, + Node, unknown, unknown, unknown, @@ -29,12 +31,12 @@ export const make = () => { createInstance(type, props) { let instance: Node if (type === "custom") { - if (!Predicate.hasProperty(props, "class")) + if (!hasProperty(props, "class")) throw new Error("Property 'class' required when using the 'custom' intrinsic type") instance = new (props.class as any)() } else { - const className = String.capitalize(type) + const className = snakeToPascal(camelToSnake(type)) if (!ClassDB.class_exists(className)) throw new Error(`Class is invalid: '${className}' (declared as '${type}') is not a valid engine or GDExtension class`) instance = ClassDB.instantiate(className) @@ -79,14 +81,14 @@ export const make = () => { return false }, - prepareForCommit() {}, + prepareForCommit() { return null }, resetAfterCommit() {}, getRootHostContext() { return {}; }, - getChildHostContext(parentHostContext, type, rootContainer) { + getChildHostContext(parentHostContext, _type, _rootContainer) { return parentHostContext }, @@ -103,7 +105,7 @@ export const make = () => { }, getPublicInstance(instance) { - return instance; + return instance }, resolveUpdatePriority() { @@ -142,12 +144,12 @@ export const make = () => { }) } -const applyNextProps = (instance: Node, nextProps: Record) => { +const applyNextProps = (instance: Node, nextProps: Record) => { Object.keys(nextProps).forEach(name => { (instance as any)[name] = nextProps[name] }) - if (Predicate.hasProperty(nextProps, "name")) { + if (hasProperty(nextProps, "name")) { if (typeof nextProps.name !== "string") throw new Error("Prop 'name' should be a string") instance.set_name(nextProps.name) diff --git a/packages/react-godot-renderer/src/utils.ts b/packages/react-godot-renderer/src/utils.ts new file mode 100644 index 0000000..bd35a76 --- /dev/null +++ b/packages/react-godot-renderer/src/utils.ts @@ -0,0 +1,16 @@ +export const isRecordOrArray = (input: unknown): input is { [x: PropertyKey]: unknown } => + typeof input === "object" && input !== null +// biome-ignore lint/complexity/noBannedTypes: it's completely fine +export const isFunction = (input: unknown): input is Function => typeof input === "function" +export const isObject = (input: unknown): input is object => isRecordOrArray(input) || isFunction(input) +export const hasProperty =

(self: unknown, property: P): self is { [K in P]: unknown } => + isObject(self) && (property in self) + +export const camelToSnake = (self: string): string => self.replace(/([A-Z])/g, "_$1").toLowerCase() +export const snakeToPascal = (self: string): string => { + let str = self[0].toUpperCase() + for (let i = 1; i < self.length; i++) { + str += self[i] === "_" ? self[++i].toUpperCase() : self[i] + } + return str +}