最後活躍 1785852451

Interactive Knowledge Graph Debugger

ZZ's Avatar ZZ 已修改 1785852451. 還原成這個修訂版本

沒有任何變更

ZZ's Avatar ZZ 已修改 1785852414. 還原成這個修訂版本

沒有任何變更

ZZ's Avatar ZZ 已修改 1785852365. 還原成這個修訂版本

沒有任何變更

ZZ's Avatar ZZ 已修改 1785851897. 還原成這個修訂版本

1 file changed, 0 insertions, 0 deletions

Godot-Scenetree.md 重命名為 Scenetree.md

檔案名稱與重新命名前相同

ZZ's Avatar ZZ 已修改 1785851879. 還原成這個修訂版本

1 file changed, 245 insertions

network_graph3.json(檔案已創建)

@@ -0,0 +1,245 @@
1 + {
2 + "@context": "https://schema.org",
3 + "@graph": [
4 + {
5 + "@id": "#alice_chen",
6 + "name": "Alice Chen\nCEO",
7 + "jobTitle": "Chief Executive Officer",
8 + "knows": [
9 + {"@id": "#bob_smith"}, {"@id": "#carol_white"}, {"@id": "#dave_brown"}
10 + ]
11 + },
12 + {
13 + "@id": "#bob_smith",
14 + "name": "Bob Smith\nCTO",
15 + "jobTitle": "Chief Technology Officer",
16 + "knows": [
17 + {"@id": "#alice_chen"}, {"@id": "#eve_johnson"}, {"@id": "#frank_miller"}, {"@id": "#george_davis"}
18 + ]
19 + },
20 + {
21 + "@id": "#carol_white",
22 + "name": "Carol White\nCFO",
23 + "jobTitle": "Chief Financial Officer",
24 + "knows": [
25 + {"@id": "#alice_chen"}, {"@id": "#dave_brown"}, {"@id": "#henry_wilson"}
26 + ]
27 + },
28 + {
29 + "@id": "#dave_brown",
30 + "name": "Dave Brown\nCOO",
31 + "jobTitle": "Chief Operating Officer",
32 + "knows": [
33 + {"@id": "#alice_chen"}, {"@id": "#henry_wilson"}, {"@id": "#frank_miller"}
34 + ]
35 + },
36 + {
37 + "@id": "#eve_johnson",
38 + "name": "Eve Johnson\nVP Engineering",
39 + "jobTitle": "Vice President of Engineering",
40 + "knows": [
41 + {"@id": "#bob_smith"}, {"@id": "#jack_anderson"}, {"@id": "#kelly_thomas"}, {"@id": "#liam_martin"}
42 + ]
43 + },
44 + {
45 + "@id": "#frank_miller",
46 + "name": "Frank Miller\nVP Sales",
47 + "jobTitle": "Vice President of Sales",
48 + "knows": [
49 + {"@id": "#dave_brown"}, {"@id": "#nora_lee"}, {"@id": "#oscar_clark"}, {"@id": "#mia_garcia"}
50 + ]
51 + },
52 + {
53 + "@id": "#george_davis",
54 + "name": "George Davis\nVP Product",
55 + "jobTitle": "Vice President of Product",
56 + "knows": [
57 + {"@id": "#bob_smith"}, {"@id": "#mia_garcia"}, {"@id": "#liam_martin"}, {"@id": "#paul_lewis"}
58 + ]
59 + },
60 + {
61 + "@id": "#henry_wilson",
62 + "name": "Henry Wilson\nHead of HR",
63 + "jobTitle": "Human Resources Director",
64 + "knows": [
65 + {"@id": "#carol_white"}, {"@id": "#quinn_adams"}, {"@id": "#rachel_king"}
66 + ]
67 + },
68 + {
69 + "@id": "#jack_anderson",
70 + "name": "Jack Anderson\nLead Dev",
71 + "jobTitle": "Senior Software Engineer",
72 + "knows": [
73 + {"@id": "#eve_johnson"}, {"@id": "#kelly_thomas"}, {"@id": "#paul_lewis"}, {"@id": "#liam_martin"}
74 + ]
75 + },
76 + {
77 + "@id": "#kelly_thomas",
78 + "name": "Kelly Thomas\nQA Lead",
79 + "jobTitle": "Quality Assurance Manager",
80 + "knows": [
81 + {"@id": "#eve_johnson"}, {"@id": "#jack_anderson"}, {"@id": "#quinn_adams"}
82 + ]
83 + },
84 + {
85 + "@id": "#liam_martin",
86 + "name": "Liam Martin\nProduct Manager",
87 + "jobTitle": "Senior Product Manager",
88 + "knows": [
89 + {"@id": "#eve_johnson"}, {"@id": "#george_davis"}, {"@id": "#jack_anderson"}, {"@id": "#mia_garcia"}
90 + ]
91 + },
92 + {
93 + "@id": "#mia_garcia",
94 + "name": "Mia Garcia\nUX Designer",
95 + "jobTitle": "Lead UX/UI Designer",
96 + "knows": [
97 + {"@id": "#frank_miller"}, {"@id": "#george_davis"}, {"@id": "#liam_martin"}
98 + ]
99 + },
100 + {
101 + "@id": "#nora_lee",
102 + "name": "Nora Lee\nSales Rep",
103 + "jobTitle": "Enterprise Sales Account Executive",
104 + "knows": [
105 + {"@id": "#frank_miller"}, {"@id": "#oscar_clark"}
106 + ]
107 + },
108 + {
109 + "@id": "#oscar_clark",
110 + "name": "Oscar Clark\nSales Rep",
111 + "jobTitle": "Account Manager",
112 + "knows": [
113 + {"@id": "#frank_miller"}, {"@id": "#nora_lee"}
114 + ]
115 + },
116 + {
117 + "@id": "#paul_lewis",
118 + "name": "Paul Lewis\nData Scientist",
119 + "jobTitle": "Staff Data Scientist",
120 + "knows": [
121 + {"@id": "#george_davis"}, {"@id": "#jack_anderson"}, {"@id": "#eve_johnson"}
122 + ]
123 + },
124 + {
125 + "@id": "#quinn_adams",
126 + "name": "Quinn Adams\nIntern",
127 + "jobTitle": "Engineering Intern",
128 + "knows": [
129 + {"@id": "#henry_wilson"}, {"@id": "#kelly_thomas"}
130 + ]
131 + },
132 + {
133 + "@id": "#rachel_king",
134 + "name": "Rachel King\nIntern",
135 + "jobTitle": "Marketing Intern",
136 + "knows": [
137 + {"@id": "#henry_wilson"}, {"@id": "#frank_miller"}
138 + ]
139 + },
140 + {
141 + "@id": "#steve_ross",
142 + "name": "Steve Ross\nBackend Dev",
143 + "jobTitle": "Software Engineer II",
144 + "knows": [
145 + {"@id": "#jack_anderson"}, {"@id": "#paul_lewis"}
146 + ]
147 + },
148 + {
149 + "@id": "#tina_turner",
150 + "name": "Tina Turner\nFrontend Dev",
151 + "jobTitle": "Software Engineer II",
152 + "knows": [
153 + {"@id": "#jack_anderson"}, {"@id": "#mia_garcia"}
154 + ]
155 + },
156 + {
157 + "@id": "#ursula_march",
158 + "name": "Ursula March\nAccountant",
159 + "jobTitle": "Senior Accountant",
160 + "knows": [
161 + {"@id": "#carol_white"}
162 + ]
163 + },
164 + {
165 + "@id": "#victor_stone",
166 + "name": "Victor Stone\nRecruiter",
167 + "jobTitle": "Technical Recruiter",
168 + "knows": [
169 + {"@id": "#henry_wilson"}, {"@id": "#eve_johnson"}
170 + ]
171 + },
172 + {
173 + "@id": "#wendy_wilson",
174 + "name": "Wendy Wilson\nOffice Mgr",
175 + "jobTitle": "Office Manager",
176 + "knows": [
177 + {"@id": "#henry_wilson"}, {"@id": "#dave_brown"}
178 + ]
179 + },
180 + {
181 + "@id": "#xavier_mercer",
182 + "name": "Xavier Mercer\nSecurity",
183 + "jobTitle": "IT Security Specialist",
184 + "knows": [
185 + {"@id": "#bob_smith"}, {"@id": "#eve_johnson"}
186 + ]
187 + },
188 + {
189 + "@id": "#yara_helmy",
190 + "name": "Yara Helmy\nCompliance",
191 + "jobTitle": "Legal & Compliance Officer",
192 + "knows": [
193 + {"@id": "#carol_white"}, {"@id": "#alice_chen"}
194 + ]
195 + },
196 + {
197 + "@id": "#zack_berry",
198 + "name": "Zack Berry\nSupport Lead",
199 + "jobTitle": "Customer Success Manager",
200 + "knows": [
201 + {"@id": "#frank_miller"}, {"@id": "#eve_johnson"}, {"@id": "#liam_martin"}
202 + ]
203 + },
204 + {
205 + "@id": "#amy_pond",
206 + "name": "Amy Pond\nSupport",
207 + "jobTitle": "Tier 2 Support Specialist",
208 + "knows": [
209 + {"@id": "#zack_berry"}, {"@id": "#jack_anderson"}
210 + ]
211 + },
212 + {
213 + "@id": "#brian_cox",
214 + "name": "Brian Cox\nDevOps",
215 + "jobTitle": "Infrastructure Engineer",
216 + "knows": [
217 + {"@id": "#eve_johnson"}, {"@id": "#xavier_mercer"}
218 + ]
219 + },
220 + {
221 + "@id": "#chloe_law",
222 + "name": "Chloe Law\nLegal",
223 + "jobTitle": "General Counsel",
224 + "knows": [
225 + {"@id": "#yara_helmy"}, {"@id": "#alice_chen"}, {"@id": "#carol_white"}
226 + ]
227 + },
228 + {
229 + "@id": "#derek_ryusaki",
230 + "name": "Derek Ryusaki\nArchitect",
231 + "jobTitle": "Solutions Architect",
232 + "knows": [
233 + {"@id": "#bob_smith"}, {"@id": "#jack_anderson"}, {"@id": "#liam_martin"}
234 + ]
235 + },
236 + {
237 + "@id": "#elaine_benese",
238 + "name": "Elaine Benese\nAdmin",
239 + "jobTitle": "Executive Assistant",
240 + "knows": [
241 + {"@id": "#alice_chen"}, {"@id": "#wendy_wilson"}
242 + ]
243 + }
244 + ]
245 + }

ZZ's Avatar ZZ 已修改 1785851842. 還原成這個修訂版本

2 files changed, 342 insertions

Godot-Scenetree.md(檔案已創建)

@@ -0,0 +1,5 @@
1 + ```
2 + ┖╴GraphNode
3 + ┠╴ColorRect
4 + ┃ ┖╴Label
5 + ```

MainGraphLoader.gd(檔案已創建)

@@ -0,0 +1,337 @@
1 + extends Node2D
2 +
3 + const GRAPH_FILE_PATH = "res://network_graph3.json"
4 + var graph_node_scene: PackedScene = preload("res://GraphNode.tscn")
5 + #@onready var status_label: Label = $ColorRect/Label
6 + @onready var status_label: RichTextLabel = $ColorRect/Label
7 + var _stable_frames: int = 0
8 +
9 + # ==========================================
10 + # PHYSICS TUNING & MOBILE OPTIMIZATIONS
11 + # ==========================================
12 + const REPULSION_STRENGTH: float = 45000.0
13 + const SPRING_STRENGTH: float = 2.5
14 + const REST_LENGTH: float = 330.0
15 + const DAMPING: float = 0.95
16 + const MAX_SPEED: float = 500.0 # Mobile safety cap
17 + #const PHYSICS_SLEEP_THRESHOLD: float = 1.10 # Stops sim when stable (saves mobile CPU/battery)
18 + const PHYSICS_SLEEP_THRESHOLD: float = 250.0 # Avg force per node allowed before sleeping
19 + const STABLE_FRAME_REQUIREMENT: int = 60 # Must stay stable for ~1.5s (at 60fps) to sleep
20 +
21 + # State & Caching
22 + var spawned_nodes: Dictionary = {}
23 + var node_ids: Array[String] = []
24 + var relationships: Array = []
25 + var lines: Array[Line2D] = []
26 + var node_colors: Dictionary = {}
27 +
28 + var viewport_size: Vector2i = Vector2i.ZERO
29 + var physics_active: bool = true
30 +
31 + # FPS & Metrics Tracking
32 + var _last_metrics_update: float = 0.0
33 + const METRICS_UPDATE_INTERVAL: float = 0.2 # Update UI 5x/sec to prevent GC spikes
34 + var _total_node_count: int = 0
35 + var _total_edge_count: int = 0
36 +
37 + # ✅ NEW: Store benchmark results for the dashboard
38 + var _benchmark_read_ms: float = 0.0
39 + var _benchmark_parse_ms: float = 0.0
40 +
41 + # ==========================================
42 + # INITIALIZATION
43 + # ==========================================
44 + func _ready() -> void:
45 + viewport_size = get_viewport_rect().size
46 + get_window().size_changed.connect(_on_window_resized)
47 +
48 + # FIX: Enable BBCode parsing so [color=...] tags render as actual colors
49 + status_label.bbcode_enabled = true
50 +
51 + var json_data: Dictionary = load_json_ld(GRAPH_FILE_PATH)
52 + if json_data.is_empty():
53 + push_warning("JSON-LD failed to load or is empty.")
54 + return
55 +
56 + var graph_array = json_data.get("@graph", [])
57 + var nodes_map: Dictionary = {}
58 +
59 + for item in graph_array:
60 + if item is Dictionary and item.has("@id"):
61 + nodes_map[item["@id"]] = item
62 +
63 + build_graph_network(nodes_map)
64 +
65 + _total_node_count = spawned_nodes.size()
66 + _total_edge_count = relationships.size()
67 +
68 + benchmark_json_ld()
69 + print_rich("[color=cyan]Graph Loaded: ", str(_total_node_count), " nodes, ", str(_total_edge_count), " edges.[/color]")
70 +
71 + # ==========================================
72 + # CORE FUNCTIONS
73 + # ==========================================
74 + func _process(delta: float) -> void:
75 + if physics_active:
76 + _run_physics_step(min(delta, 0.03))
77 +
78 + _update_lines()
79 + _update_metrics(delta, Time.get_ticks_msec())
80 +
81 + # ==========================================
82 + # INPUT HANDLING (Mouse + Touch)
83 + # ==========================================
84 + func _unhandled_input(event: InputEvent) -> void:
85 + var screen_pos = Vector2.ZERO
86 + var is_click_like = false
87 +
88 + if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
89 + screen_pos = event.position
90 + is_click_like = true
91 + elif event is InputEventScreenTouch:
92 + screen_pos = event.position
93 + is_click_like = true
94 +
95 + if not is_click_like:
96 + return
97 +
98 + if event.is_pressed():
99 + _attempt_grab_node(screen_pos, true)
100 + else:
101 + _attempt_release_drag()
102 +
103 + func _on_window_resized() -> void:
104 + viewport_size = get_viewport_rect().size
105 +
106 + # ==========================================
107 + # PHYSICS & UPDATE LOOPS
108 + # ==========================================
109 + func _run_physics_step(dt: float) -> void:
110 + var total_force_magnitude: float = 0.0
111 + var node_count = node_ids.size()
112 +
113 + for i in range(node_count):
114 + var id_a = node_ids[i]
115 + var node_a = spawned_nodes[id_a]
116 + if node_a.is_dragging: continue
117 +
118 + var pos_a = node_a.position
119 + var vel_a = node_a.velocity
120 +
121 + for j in range(i + 1, node_count):
122 + var id_b = node_ids[j]
123 + var node_b = spawned_nodes[id_b]
124 + if node_b.is_dragging: continue
125 +
126 + var dir = pos_a - node_b.position
127 + var dist_sq = dir.dot(dir)
128 + if dist_sq < 1.0 or dist_sq > REPULSION_STRENGTH * 2.0: continue
129 +
130 + var dist = sqrt(dist_sq)
131 + var force = (REPULSION_STRENGTH / dist_sq) * dt
132 + var f_vec = dir.normalized() * force
133 +
134 + vel_a += f_vec
135 + node_b.velocity -= f_vec if not node_b.is_dragging else Vector2.ZERO
136 +
137 + total_force_magnitude += absf(force)
138 +
139 + for rel in relationships:
140 + var node_a = rel[0]
141 + var node_b = rel[1]
142 +
143 + var dir = node_b.position - node_a.position
144 + var dist = dir.length()
145 + if dist < 0.1: continue
146 +
147 + var displacement = dist - REST_LENGTH
148 + var force_vec = dir.normalized() * (SPRING_STRENGTH * displacement) * dt
149 +
150 + if not node_a.is_dragging: node_a.velocity += force_vec
151 + if not node_b.is_dragging: node_b.velocity -= force_vec
152 +
153 + total_force_magnitude += absf(SPRING_STRENGTH * displacement)
154 +
155 + for id in node_ids:
156 + var node = spawned_nodes[id]
157 + if node.is_dragging: continue
158 +
159 + node.position += node.velocity * dt
160 + node.velocity *= DAMPING
161 +
162 + if node.velocity.length() > MAX_SPEED:
163 + node.velocity = node.velocity.normalized() * MAX_SPEED
164 +
165 + var gp = node.global_position
166 + gp.x = clampf(gp.x, 50.0, float(viewport_size.x - 200))
167 + gp.y = clampf(gp.y, 50.0, float(viewport_size.y - 100))
168 + node.global_position = gp
169 +
170 + # ==========================================
171 + # STABILIZATION & AUTO-SLEEP LOGIC
172 + # ==========================================
173 + var avg_force_per_node = total_force_magnitude / max(node_count, 1.0)
174 +
175 + if avg_force_per_node < PHYSICS_SLEEP_THRESHOLD:
176 + _stable_frames += 1
177 + else:
178 + _stable_frames = 0
179 + if _stable_frames >= STABLE_FRAME_REQUIREMENT:
180 + physics_active = false
181 +
182 +
183 +
184 +
185 + func _update_lines() -> void:
186 + for i in range(relationships.size()):
187 + var line = lines[i]
188 + var rel = relationships[i]
189 + var offset = Vector2(75.0, 25.0)
190 +
191 + line.points = [
192 + Vector2(rel[0].position + offset),
193 + Vector2(rel[1].position + offset)
194 + ]
195 +
196 + # ✅ UPDATED: Injects benchmark data into the persistent dashboard
197 + func _update_metrics(_delta: float, current_time_ms: int) -> void:
198 + if current_time_ms - _last_metrics_update < METRICS_UPDATE_INTERVAL * 1000.0:
199 + return
200 +
201 + var fps = Engine.get_frames_per_second()
202 + var frame_time = 1000.0 / fps if fps > 0 else 0.0
203 + var phys_color = "#7ee787" if physics_active else "#e74c3c"
204 + var phys_status = "ACTIVE" if physics_active else "SLEEPING"
205 +
206 + status_label.text = ("[color=#7ee787]▮ MOBILE DASHBOARD[/color]\n" +
207 + "FPS: [color=cyan]%d[/color] | Frame: [color=orange]%.2f ms[/color]\n" +
208 + "Nodes: %d | Edges: %d\n" +
209 + "Physics: [color=%s]%s[/color]\n" +
210 + "[color=#ffdd57]📊 BENCHMARK:[/color] Read: %.1f ms | Parse: %.1f ms") \
211 + % [fps, frame_time, _total_node_count, _total_edge_count,
212 + phys_color, phys_status, _benchmark_read_ms, _benchmark_parse_ms]
213 +
214 +
215 + _last_metrics_update = current_time_ms
216 +
217 + # ==========================================
218 + # DRAG SYSTEM
219 + # ==========================================
220 + func _attempt_grab_node(screen_pos: Vector2, press: bool) -> void:
221 + if not press: return
222 +
223 + var best_dist: float = 50.0
224 + var grabbed_id: String = ""
225 +
226 + for id in node_ids:
227 + var node = spawned_nodes[id]
228 + var dist = (node.position - screen_pos).length()
229 + if dist < best_dist:
230 + best_dist = dist
231 + grabbed_id = id
232 +
233 + if not grabbed_id.is_empty():
234 + spawned_nodes[grabbed_id].is_dragging = true
235 +
236 + #func _attempt_release_drag() -> void:
237 + #for node in spawned_nodes.values():
238 + #if node.is_dragging:
239 + #node.is_dragging = false
240 + #physics_active = true
241 + func _attempt_release_drag() -> void:
242 + for node in spawned_nodes.values():
243 + if node.is_dragging:
244 + node.is_dragging = false
245 + physics_active = true
246 + _stable_frames = 0 # ✅ Reset stabilization counter on interaction
247 +
248 + # ==========================================
249 + # BUILDERS & UTILITIES
250 + # ==========================================
251 + func build_graph_network(nodes_map: Dictionary) -> void:
252 + var keys = nodes_map.keys()
253 + var total_nodes = keys.size()
254 + var center = get_viewport_rect().size * 0.5
255 +
256 + for i in range(total_nodes):
257 + var node_id = keys[i]
258 + var node_data = nodes_map[node_id]
259 + var new_visual_node = graph_node_scene.instantiate()
260 +
261 + if not new_visual_node.has_method("setup"):
262 + new_visual_node.set_script(preload("res://GraphNode.gd"))
263 +
264 + add_child(new_visual_node)
265 +
266 + var angle = (TAU / total_nodes) * i
267 + var radius = 400.0
268 + new_visual_node.position = center + Vector2.from_angle(angle) * radius
269 + new_visual_node.position += Vector2(randf_range(-50, 50), randf_range(-50, 50))
270 + new_visual_node.setup(node_id, node_data.get("name", "Unknown"))
271 +
272 + spawned_nodes[node_id] = new_visual_node
273 + node_ids.append(node_id)
274 + node_colors[node_id] = _get_unique_color(node_id)
275 +
276 + for node_id in keys:
277 + var node_data = nodes_map[node_id]
278 + if not node_data.has("knows"): continue
279 +
280 + var raw_connections = node_data["knows"]
281 + var connection_list: Array = raw_connections if raw_connections is Array else [raw_connections]
282 +
283 + for conn in connection_list:
284 + if conn is Dictionary and conn.has("@id") and spawned_nodes.has(conn["@id"]):
285 + relationships.append([spawned_nodes[node_id], spawned_nodes[conn["@id"]]])
286 +
287 + var line = Line2D.new()
288 + line.width = 5.0
289 + line.z_index = -1
290 +
291 + var grad = Gradient.new()
292 + grad.set_color(0, node_colors[node_id])
293 + grad.set_color(1, node_colors[conn["@id"]])
294 + line.gradient = grad
295 +
296 + add_child(line)
297 + lines.append(line)
298 +
299 + func _get_unique_color(id_string: String) -> Color:
300 + var h = hash(id_string)
301 + return Color8(
302 + max(abs(h) % 256, 128),
303 + max((abs(h) >> 8) % 256, 128),
304 + max((abs(h) >> 16) % 256, 128)
305 + )
306 +
307 + func load_json_ld(path: String) -> Dictionary:
308 + if not FileAccess.file_exists(path):
309 + push_error("JSON-LD file not found at: " + path)
310 + return {}
311 +
312 + var file = FileAccess.open(path, FileAccess.READ)
313 + var json_string = file.get_as_text()
314 + file.close()
315 + var parsed_data = JSON.parse_string(json_string)
316 + return parsed_data if parsed_data is Dictionary else {}
317 +
318 + # ✅ UPDATED: Saves timing data to class variables instead of just printing
319 + func benchmark_json_ld() -> void:
320 + if not FileAccess.file_exists(GRAPH_FILE_PATH):
321 + return
322 +
323 + var start_time = Time.get_ticks_msec()
324 + var file = FileAccess.open(GRAPH_FILE_PATH, FileAccess.READ)
325 + var json_string = file.get_as_text()
326 + file.close()
327 + _benchmark_read_ms = float(Time.get_ticks_msec() - start_time)
328 +
329 + start_time = Time.get_ticks_msec()
330 + var _parsed = JSON.parse_string(json_string)
331 + _benchmark_parse_ms = float(Time.get_ticks_msec() - start_time)
332 +
333 + #print_rich("[color=yellow]📊 BENCHMARK: Read: %.1f ms | Parse: %.1f ms[/color]" % [_benchmark_read_ms, _benchmark_parse_ms])
334 + print_rich("[color=yellow]📊 BENCHMARK: Read: {read} ms | Parse: {parse} [/color]".format({
335 + "read": "%0.1f" % _benchmark_read_ms,
336 + "parse": "%0.1f" % _benchmark_parse_ms
337 + }))
上一頁 下一頁