glre 0.21.0 → 0.22.0

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 (48) hide show
  1. package/README.md +150 -80
  2. package/dist/index.d.ts +524 -7
  3. package/dist/index.js +46 -13
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +46 -13
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/native.d.ts +6 -64
  8. package/dist/native.js +46 -13
  9. package/dist/native.js.map +1 -1
  10. package/dist/native.mjs +46 -13
  11. package/dist/native.mjs.map +1 -1
  12. package/dist/react.d.ts +7 -11
  13. package/dist/react.js +46 -13
  14. package/dist/react.js.map +1 -1
  15. package/dist/react.mjs +46 -13
  16. package/dist/react.mjs.map +1 -1
  17. package/dist/solid.d.ts +6 -63
  18. package/dist/solid.js +46 -13
  19. package/dist/solid.js.map +1 -1
  20. package/dist/solid.mjs +46 -13
  21. package/dist/solid.mjs.map +1 -1
  22. package/package.json +25 -60
  23. package/src/code/glsl.ts +186 -0
  24. package/src/code/wgsl.ts +170 -0
  25. package/src/index.ts +105 -0
  26. package/src/native.ts +24 -0
  27. package/src/node/cache.ts +67 -0
  28. package/src/node/const.ts +147 -0
  29. package/src/node/conv.ts +122 -0
  30. package/src/node/index.ts +96 -0
  31. package/src/node/node.ts +114 -0
  32. package/src/node/types.ts +101 -0
  33. package/src/node/uniform.ts +99 -0
  34. package/src/react.ts +18 -0
  35. package/src/solid.ts +15 -0
  36. package/src/types.ts +90 -0
  37. package/src/utils.ts +53 -0
  38. package/src/webgl/buffer.ts +78 -0
  39. package/src/webgl/index.ts +79 -0
  40. package/src/webgl/program.ts +61 -0
  41. package/src/webgl/shader.ts +60 -0
  42. package/src/webgl/texture.ts +93 -0
  43. package/src/webgpu/buffer.ts +96 -0
  44. package/src/webgpu/device.ts +91 -0
  45. package/src/webgpu/index.ts +40 -0
  46. package/src/webgpu/pipeline.ts +94 -0
  47. package/src/webgpu/texture.ts +139 -0
  48. package/dist/types-f429c8b1.d.ts +0 -79
@@ -0,0 +1,186 @@
1
+ import { is } from './../utils'
2
+ import type { Node, NodeType, X, ConversionContext } from '../node'
3
+
4
+ // GLSLコード生成コンテキスト
5
+ interface GLSLContext extends ConversionContext {
6
+ target: 'webgl'
7
+ precision: 'lowp' | 'mediump' | 'highp'
8
+ version: '100' | '300 es'
9
+ }
10
+
11
+ // ノードからGLSLコードを生成
12
+ export const nodeToGLSL = (
13
+ nodeProxy: X,
14
+ context?: Partial<GLSLContext>
15
+ ): string => {
16
+ const ctx: GLSLContext = {
17
+ target: 'webgl',
18
+ precision: 'mediump',
19
+ version: '300 es',
20
+ nodes: new Map(),
21
+ variables: new Map(),
22
+ functions: new Map(),
23
+ ...context,
24
+ }
25
+
26
+ return generateGLSLExpression(nodeProxy as any, ctx)
27
+ }
28
+
29
+ // GLSL式を生成
30
+ const generateGLSLExpression = (node: Node, context: GLSLContext): string => {
31
+ if (!node) return '0.0'
32
+ // 値ノード
33
+ if (!is.und(node.value)) return formatGLSLValue(node.value, node.type)
34
+ // プロパティアクセス(スウィズル)
35
+ if (node.property && node.parent) {
36
+ const parentExpr = generateGLSLExpression(node.parent, context)
37
+ return `${parentExpr}.${node.property}`
38
+ }
39
+ // 演算子ノード
40
+ if (node.operator && node.children && node.children.length >= 2)
41
+ return generateGLSLOperator(node, context)
42
+ // 数学関数ノード
43
+ if (node.mathFunction && node.children && node.children.length >= 1)
44
+ return generateGLSLMathFunction(node, context)
45
+ return '0.0'
46
+ }
47
+
48
+ // GLSL値をフォーマット
49
+ const formatGLSLValue = (value: any, type: NodeType): string => {
50
+ if (type === 'float') {
51
+ const num = Number(value)
52
+ return num % 1 === 0 ? `${num}.0` : `${num}`
53
+ }
54
+ if (type === 'int') return `${Math.floor(Number(value))}`
55
+ if (type === 'bool') return Boolean(value) ? 'true' : 'false'
56
+ if (is.arr(value)) {
57
+ const values = value
58
+ .map((v) => {
59
+ const num = Number(v)
60
+ return num % 1 === 0 ? `${num}.0` : `${num}`
61
+ })
62
+ .join(', ')
63
+ if (type === 'vec2') return `vec2(${values})`
64
+ if (type === 'vec3') return `vec3(${values})`
65
+ if (type === 'vec4') return `vec4(${values})`
66
+ if (type === 'color') return `vec3(${values})`
67
+ }
68
+ return '0.0'
69
+ }
70
+
71
+ // GLSL演算子を生成
72
+ const generateGLSLOperator = (node: Node, context: GLSLContext): string => {
73
+ if (!node.children || node.children.length < 2) return '0.0'
74
+ const left = generateGLSLExpression(node.children[0], context)
75
+ const right = generateGLSLExpression(node.children[1], context)
76
+ if (node.operator === 'add') return `(${left} + ${right})`
77
+ if (node.operator === 'sub') return `(${left} - ${right})`
78
+ if (node.operator === 'mul') return `(${left} * ${right})`
79
+ if (node.operator === 'div') return `(${left} / ${right})`
80
+ if (node.operator === 'mod') return `mod(${left}, ${right})`
81
+ if (node.operator === 'equal') return `(${left} == ${right})`
82
+ if (node.operator === 'notEqual') return `(${left} != ${right})`
83
+ if (node.operator === 'lessThan') return `(${left} < ${right})`
84
+ if (node.operator === 'lessThanEqual') return `(${left} <= ${right})`
85
+ if (node.operator === 'greaterThan') return `(${left} > ${right})`
86
+ if (node.operator === 'greaterThanEqual') return `(${left} >= ${right})`
87
+ if (node.operator === 'and') return `(${left} && ${right})`
88
+ if (node.operator === 'or') return `(${left} || ${right})`
89
+ return `(${left} + ${right})`
90
+ }
91
+
92
+ // GLSL数学関数を生成
93
+ const generateGLSLMathFunction = (node: Node, context: GLSLContext): string => {
94
+ if (!node.children || node.children.length < 1) return '0.0'
95
+ const fun = node.mathFunction
96
+ const args = node.children.map((child) => {
97
+ return generateGLSLExpression(child, context)
98
+ })
99
+ const [x, y, z] = args
100
+ // @TODO FIX
101
+ // if (fun === 'toVar') return x // toVarは変数化のヒントなので、そのまま返す
102
+ // 単項関数
103
+ if (args.length === 1) {
104
+ if (fun === 'abs') return `abs(${x})`
105
+ if (fun === 'acos') return `acos(${x})`
106
+ if (fun === 'asin') return `asin(${x})`
107
+ if (fun === 'atan') return `atan(${x})`
108
+ if (fun === 'ceil') return `ceil(${x})`
109
+ if (fun === 'cos') return `cos(${x})`
110
+ if (fun === 'floor') return `floor(${x})`
111
+ if (fun === 'fract') return `fract(${x})`
112
+ if (fun === 'length') return `length(${x})`
113
+ if (fun === 'normalize') return `normalize(${x})`
114
+ if (fun === 'sin') return `sin(${x})`
115
+ if (fun === 'sqrt') return `sqrt(${x})`
116
+ if (fun === 'tan') return `tan(${x})`
117
+ }
118
+ // 二項関数
119
+ if (args.length === 2) {
120
+ if (fun === 'atan2') return `atan(${x}, ${y})`
121
+ if (fun === 'pow') return `pow(${x}, ${y})`
122
+ if (fun === 'min') return `min(${x}, ${y})`
123
+ if (fun === 'max') return `max(${x}, ${y})`
124
+ if (fun === 'dot') return `dot(${x}, ${y})`
125
+ if (fun === 'cross') return `cross(${x}, ${y})`
126
+ if (fun === 'distance') return `distance(${x}, ${y})`
127
+ if (fun === 'reflect') return `reflect(${x}, ${y})`
128
+ }
129
+ // 三項関数
130
+ if (args.length === 3) {
131
+ if (fun === 'mix') return `mix(${x}, ${y}, ${z})`
132
+ if (fun === 'clamp') return `clamp(${x}, ${y}, ${z})`
133
+ if (fun === 'smoothstep') return `smoothstep(${x}, ${y}, ${z})`
134
+ if (fun === 'refract') return `refract(${x}, ${y}, ${z})`
135
+ }
136
+
137
+ return x || '0.0'
138
+ }
139
+
140
+ // 値からGLSL型を推定
141
+ const inferGLSLType = (value: unknown): string => {
142
+ if (is.num(value)) return 'float'
143
+ if (is.bol(value)) return 'bool'
144
+ if (is.arr(value)) {
145
+ const len = value.length
146
+ if (len === 2) return 'vec2'
147
+ if (len === 3) return 'vec3'
148
+ if (len === 4) return 'vec4'
149
+ }
150
+ return 'float'
151
+ }
152
+
153
+ // 完全なGLSLシェーダーを生成
154
+ export const glsl = (
155
+ fragmentNode: X,
156
+ options?: {
157
+ precision?: 'lowp' | 'mediump' | 'highp'
158
+ version?: '100' | '300 es'
159
+ uniforms?: Record<string, any>
160
+ }
161
+ ) => {
162
+ const precision = options?.precision || 'mediump'
163
+ const version = options?.version || '300 es'
164
+ const is300ES = version === '300 es'
165
+ const fragment = nodeToGLSL(fragmentNode)
166
+ let shader = ''
167
+ if (is300ES) shader += '#version 300 es\n'
168
+ shader += `precision ${precision} float;\n\n`
169
+ // ユニフォーム変数の追加
170
+ if (options?.uniforms) {
171
+ Object.entries(options.uniforms).forEach(([name, value]) => {
172
+ const type = inferGLSLType(value)
173
+ shader += `uniform ${type} ${name};\n`
174
+ })
175
+ shader += '\n'
176
+ }
177
+ // 出力変数
178
+ if (is300ES) shader += 'out vec4 fragColor;\n\n'
179
+ shader += 'void main() {\n'
180
+ shader += is300ES
181
+ ? ` fragColor = ${fragment};\n`
182
+ : ` gl_FragColor = ${fragment};\n`
183
+ shader += '}\n'
184
+
185
+ return shader
186
+ }
@@ -0,0 +1,170 @@
1
+ import { is } from '../utils'
2
+ import type { Node, NodeType, ConversionContext, X } from '../node'
3
+
4
+ // WGSLコード生成コンテキスト
5
+ interface WGSLContext extends ConversionContext {
6
+ target: 'webgpu'
7
+ }
8
+
9
+ // ノードからWGSLコードを生成
10
+ export const nodeToWGSL = (
11
+ nodeProxy: X,
12
+ context?: Partial<WGSLContext>
13
+ ): string => {
14
+ const ctx: WGSLContext = {
15
+ target: 'webgpu',
16
+ nodes: new Map(),
17
+ variables: new Map(),
18
+ functions: new Map(),
19
+ ...context,
20
+ }
21
+ return generateWGSLExpression(nodeProxy as any, ctx)
22
+ }
23
+
24
+ // WGSL式を生成
25
+ const generateWGSLExpression = (node: Node, context: WGSLContext): string => {
26
+ if (!node) return '0.0'
27
+ // 値ノード
28
+ if (node.value !== undefined)
29
+ return formatWGSLValue(node.value, node.type)
30
+ // プロパティアクセス(スウィズル)
31
+ if (node.property && node.parent) {
32
+ const parentExpr = generateWGSLExpression(node.parent, context)
33
+ return `${parentExpr}.${node.property}`
34
+ }
35
+ // 演算子ノード
36
+ if (node.operator && node.children && node.children.length >= 2)
37
+ return generateWGSLOperator(node, context)
38
+ // 数学関数ノード
39
+ if (node.mathFunction && node.children && node.children.length >= 1)
40
+ return generateWGSLMathFunction(node, context)
41
+ return '0.0'
42
+ }
43
+
44
+ // WGSL値をフォーマット
45
+ const formatWGSLValue = (value: any, type: NodeType): string => {
46
+ if (type === 'float') {
47
+ const num = Number(value)
48
+ return num % 1 === 0 ? `${num}.0` : `${num}`
49
+ }
50
+ if (type === 'int') return `${Math.floor(Number(value))}`
51
+ if (type === 'bool') return Boolean(value) ? 'true' : 'false'
52
+ if (is.arr(value)) {
53
+ const values = value
54
+ .map((v) => {
55
+ const num = Number(v)
56
+ return num % 1 === 0 ? `${num}.0` : `${num}`
57
+ })
58
+ .join(', ')
59
+ if (type === 'vec2') return `vec2<f32>(${values})`
60
+ if (type === 'vec3') return `vec3<f32>(${values})`
61
+ if (type === 'vec4') return `vec4<f32>(${values})`
62
+ if (type === 'color') return `vec3<f32>(${values})`
63
+ }
64
+
65
+ return '0.0'
66
+ }
67
+
68
+ // WGSL演算子を生成
69
+ const generateWGSLOperator = (node: Node, context: WGSLContext): string => {
70
+ if (!node.children || node.children.length < 2) return '0.0'
71
+ const left = generateWGSLExpression(node.children[0], context)
72
+ const right = generateWGSLExpression(node.children[1], context)
73
+ if (node.operator === 'add') return `(${left} + ${right})`
74
+ if (node.operator === 'sub') return `(${left} - ${right})`
75
+ if (node.operator === 'mul') return `(${left} * ${right})`
76
+ if (node.operator === 'div') return `(${left} / ${right})`
77
+ if (node.operator === 'mod') return `(${left} % ${right})`
78
+ if (node.operator === 'equal') return `(${left} == ${right})`
79
+ if (node.operator === 'notEqual') return `(${left} != ${right})`
80
+ if (node.operator === 'lessThan') return `(${left} < ${right})`
81
+ if (node.operator === 'lessThanEqual') return `(${left} <= ${right})`
82
+ if (node.operator === 'greaterThan') return `(${left} > ${right})`
83
+ if (node.operator === 'greaterThanEqual') return `(${left} >= ${right})`
84
+ if (node.operator === 'and') return `(${left} && ${right})`
85
+ if (node.operator === 'or') return `(${left} || ${right})`
86
+ return `(${left} + ${right})`
87
+ }
88
+
89
+ // WGSL数学関数を生成
90
+ const generateWGSLMathFunction = (node: Node, context: WGSLContext): string => {
91
+ if (!node.children || node.children.length < 1) return '0.0'
92
+ const fun = node.mathFunction
93
+ const args = node.children.map((child) =>
94
+ generateWGSLExpression(child, context)
95
+ )
96
+ const [x, y, z] = args
97
+ // 単項関数
98
+ if (args.length === 1) {
99
+ if (fun === 'abs') return `abs(${x})`
100
+ if (fun === 'acos') return `acos(${x})`
101
+ if (fun === 'asin') return `asin(${x})`
102
+ if (fun === 'atan') return `atan(${x})`
103
+ if (fun === 'ceil') return `ceil(${x})`
104
+ if (fun === 'cos') return `cos(${x})`
105
+ if (fun === 'floor') return `floor(${x})`
106
+ if (fun === 'fract') return `fract(${x})`
107
+ if (fun === 'length') return `length(${x})`
108
+ if (fun === 'normalize') return `normalize(${x})`
109
+ if (fun === 'sin') return `sin(${x})`
110
+ if (fun === 'sqrt') return `sqrt(${x})`
111
+ if (fun === 'tan') return `tan(${x})`
112
+ }
113
+ // 二項関数
114
+ if (args.length === 2) {
115
+ if (fun === 'atan2') return `atan2(${x}, ${y})`
116
+ if (fun === 'pow') return `pow(${x}, ${y})`
117
+ if (fun === 'min') return `min(${x}, ${y})`
118
+ if (fun === 'max') return `max(${x}, ${y})`
119
+ if (fun === 'dot') return `dot(${x}, ${y})`
120
+ if (fun === 'cross') return `cross(${x}, ${y})`
121
+ if (fun === 'distance') return `distance(${x}, ${y})`
122
+ if (fun === 'reflect') return `reflect(${x}, ${y})`
123
+ }
124
+ // 三項関数
125
+ if (args.length === 3) {
126
+ if (fun === 'mix') return `mix(${x}, ${y}, ${z})`
127
+ if (fun === 'clamp') return `clamp(${x}, ${y}, ${z})`
128
+ if (fun === 'smoothstep') return `smoothstep(${x}, ${y}, ${z})`
129
+ return x
130
+ }
131
+ return x || '0.0'
132
+ }
133
+
134
+ // 完全なWGSLシェーダーを生成
135
+ export const wgsl = (
136
+ fragmentNode: X,
137
+ options?: {
138
+ uniforms?: Record<string, any>
139
+ }
140
+ ) => {
141
+ let shader = ''
142
+ // ユニフォーム変数の追加
143
+ if (options?.uniforms) {
144
+ Object.entries(options.uniforms).forEach(([name, value]) => {
145
+ const type = inferWGSLType(value)
146
+ shader += `@group(0) @binding(0) var<uniform> ${name}: ${type};\n`
147
+ })
148
+ shader += '\n'
149
+ }
150
+ shader += '@fragment\n'
151
+ shader += 'fn main() -> @location(0) vec4<f32> {\n'
152
+ const fragmentExpr = nodeToWGSL(fragmentNode)
153
+ shader += ` return ${fragmentExpr};\n`
154
+ shader += '}\n'
155
+
156
+ return shader
157
+ }
158
+
159
+ // 値からWGSL型を推定
160
+ const inferWGSLType = (value: any): string => {
161
+ if (is.num(value)) return 'f32'
162
+ if (is.bol(value)) return 'bool'
163
+ if (is.arr(value)) {
164
+ const len = value.length
165
+ if (len === 2) return 'vec2<f32>'
166
+ if (len === 3) return 'vec3<f32>'
167
+ if (len === 4) return 'vec4<f32>'
168
+ }
169
+ return 'f32'
170
+ }
package/src/index.ts ADDED
@@ -0,0 +1,105 @@
1
+ import { durable, event } from 'reev'
2
+ import { createFrame, createQueue } from 'refr'
3
+ import { webgl } from './webgl/index'
4
+ import { webgpu } from './webgpu/index'
5
+ import { is, isWebGPUSupported } from './utils'
6
+ import type { EventState } from 'reev'
7
+ import type { GL } from './types'
8
+ export * from './code/glsl'
9
+ export * from './code/wgsl'
10
+ export * from './node'
11
+ export * from './types'
12
+ export * from './utils'
13
+ export * from './webgl'
14
+ export * from './webgpu'
15
+
16
+ let iTime = performance.now(),
17
+ iPrevTime = 0,
18
+ iDeltaTime = 0
19
+
20
+ export const isGL = (a: unknown): a is EventState<GL> => {
21
+ if (!is.obj(a)) return false
22
+ if ('isGL' in a) return true
23
+ return false
24
+ }
25
+
26
+ export const createGL = (props?: Partial<GL>) => {
27
+ const gl = event<Partial<GL>>({
28
+ isNative: false,
29
+ isWebGL: true,
30
+ isLoop: true,
31
+ isGL: true,
32
+ size: [0, 0],
33
+ mouse: [0, 0],
34
+ count: 6,
35
+ counter: 0,
36
+ }) as EventState<GL>
37
+
38
+ gl('mount', () => {
39
+ if (!isWebGPUSupported()) gl.isWebGL = true
40
+ if (gl.isWebGL) {
41
+ webgl(gl)
42
+ } else webgpu(gl)
43
+ gl.init()
44
+ gl.resize()
45
+ gl.frame(() => {
46
+ gl.loop()
47
+ gl.render()
48
+ return gl.isLoop
49
+ })
50
+ if (gl.isNative) return
51
+ window.addEventListener('resize', gl.resize)
52
+ gl.el.addEventListener('mousemove', gl.mousemove)
53
+ })
54
+
55
+ gl('clean', () => {
56
+ gl.frame.stop()
57
+ gl.frame.clean(gl.render)
58
+ if (gl.isNative) return
59
+ window.removeEventListener('resize', gl.resize)
60
+ gl.el.removeEventListener('mousemove', gl.mousemove)
61
+ })
62
+
63
+ gl('loop', () => {
64
+ iPrevTime = iTime
65
+ iTime = performance.now() / 1000
66
+ iDeltaTime = iTime - iPrevTime
67
+ gl.uniform({ iPrevTime, iTime, iDeltaTime })
68
+ // if (gl.fragmentNode) updateUniforms({ time: iTime }) // @TODO FIX
69
+ })
70
+
71
+ gl('resize', () => {
72
+ const w = gl.width || window.innerWidth
73
+ const h = gl.height || window.innerHeight
74
+ gl.size[0] = gl.el.width = w
75
+ gl.size[1] = gl.el.height = h
76
+ gl.uniform('iResolution', gl.size)
77
+ })
78
+
79
+ gl('mousemove', (_e: any, x = _e.clientX, y = _e.clientY) => {
80
+ const [w, h] = gl.size
81
+ const { top, left } = gl.el.getBoundingClientRect()
82
+ gl.mouse[0] = (x - top - w / 2) / (w / 2)
83
+ gl.mouse[1] = -(y - left - h / 2) / (h / 2)
84
+ gl.uniform('iMouse', gl.mouse)
85
+ })
86
+
87
+ gl.queue = createQueue()
88
+ gl.frame = createFrame()
89
+
90
+ gl.attribute = durable((key, value, iboValue) => {
91
+ gl.queue(() => void gl._attribute?.(key, value, iboValue))
92
+ })
93
+
94
+ gl.uniform = durable((key, value, isMatrix) => {
95
+ gl.queue(() => void gl._uniform?.(key, value, isMatrix))
96
+ })
97
+
98
+ gl.texture = durable((key, value) => {
99
+ gl.queue(() => void gl._texture?.(key, value))
100
+ })
101
+
102
+ return gl(props)
103
+ }
104
+
105
+ export default createGL
package/src/native.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { useState } from 'react'
2
+ import { Dimensions } from 'react-native'
3
+ import { createGL, isGL } from './index'
4
+ import type { GL } from './types'
5
+ export * from './index'
6
+
7
+ export const useGL = (props: Partial<GL> = {}) => {
8
+ return useState(() => {
9
+ const gl = isGL(props) ? props : createGL(props)
10
+ gl.ref = (ctx: any) => {
11
+ gl.el = {}
12
+ gl.gl = ctx
13
+ gl.mount()
14
+ const resize = () => {
15
+ gl.width = ctx.drawingBufferWidth
16
+ gl.height = ctx.drawingBufferHeight
17
+ gl.resize()
18
+ }
19
+ resize()
20
+ Dimensions.addEventListener('change', resize)
21
+ }
22
+ return gl
23
+ })[0]
24
+ }
@@ -0,0 +1,67 @@
1
+ import { CACHE_BOOLS, CACHE_INTS, CACHE_FLOATS } from './const'
2
+ import { node } from '.'
3
+ import type { X } from './types'
4
+
5
+ const boolCache = new Map<boolean, X>()
6
+ const intCache = new Map<number, X>()
7
+ const floatCache = new Map<number, X>()
8
+
9
+ const initializeCache = () => {
10
+ CACHE_BOOLS.forEach((value) => {
11
+ boolCache.set(value, node('bool', value))
12
+ })
13
+ CACHE_INTS.forEach((value) => {
14
+ intCache.set(value, node('int', value))
15
+ })
16
+ CACHE_FLOATS.forEach((value) => {
17
+ floatCache.set(value, node('float', value))
18
+ })
19
+ }
20
+
21
+ export const getCachedBool = (x: boolean): X => {
22
+ if (!boolCache.has(x)) initializeCache()
23
+ return boolCache.get(x) || node('bool', x)
24
+ }
25
+
26
+ // キャッシュされたintノードを取得
27
+ export const getCachedInt = (x: number): X => {
28
+ if (intCache.has(x)) return intCache.get(x)!
29
+ return node('int', x)
30
+ }
31
+
32
+ // キャッシュされたfloatノードを取得
33
+ export const getCachedFloat = (x: number): X => {
34
+ if (floatCache.has(x)) return floatCache.get(x)!
35
+ return node('float', x)
36
+ }
37
+
38
+ // ノードの重複を検出
39
+ export const findDuplicateNodes = (nodes: X[]): Map<string, X[]> => {
40
+ const duplicates = new Map<string, X[]>()
41
+ const signatures = new Map<string, X>()
42
+
43
+ nodes.forEach((nodeProxy) => {
44
+ const signature = generateNodeSignature(nodeProxy)
45
+ if (signatures.has(signature)) {
46
+ if (!duplicates.has(signature))
47
+ duplicates.set(signature, [
48
+ signatures.get(signature)!,
49
+ ])
50
+ duplicates.get(signature)!.push(nodeProxy)
51
+ } else signatures.set(signature, nodeProxy)
52
+ })
53
+ return duplicates
54
+ }
55
+
56
+ // ノードのシグネチャを生成
57
+ const generateNodeSignature = (nodeProxy: X): string => {
58
+ const parts = [
59
+ nodeProxy.type,
60
+ `${nodeProxy.value}`,
61
+ nodeProxy.property || '',
62
+ ]
63
+ return parts.join('|')
64
+ }
65
+
66
+ // 初期化を実行
67
+ initializeCache()