@stuly/anode 0.1.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.
@@ -0,0 +1,863 @@
1
+ import { Entity, Group, Link, LinkKind, Socket, SocketKind, Vec2 } from "./elements.js";
2
+ import { QuadTree, Rect } from "./layout.js";
3
+ import { HistoryManager } from "./history.js";
4
+
5
+ //#region src/core/context.ts
6
+ var Context = class {
7
+ eid = 0;
8
+ lid = 0;
9
+ sid = 0;
10
+ gid = 0;
11
+ freeEids = [];
12
+ freeLids = [];
13
+ freeSids = [];
14
+ freeGids = [];
15
+ callbackIds = 0;
16
+ freeCallbackIds = [];
17
+ entityCreateCallbacks = /* @__PURE__ */ new Map();
18
+ entityDropCallbacks = /* @__PURE__ */ new Map();
19
+ entityMoveCallbacks = /* @__PURE__ */ new Map();
20
+ socketMoveCallbacks = /* @__PURE__ */ new Map();
21
+ socketValueCallbacks = /* @__PURE__ */ new Map();
22
+ linkCreateCallbacks = /* @__PURE__ */ new Map();
23
+ linkDropCallbacks = /* @__PURE__ */ new Map();
24
+ linkUpdateCallbacks = /* @__PURE__ */ new Map();
25
+ socketCreateCallbacks = /* @__PURE__ */ new Map();
26
+ socketDropCallbacks = /* @__PURE__ */ new Map();
27
+ groupCreateCallbacks = /* @__PURE__ */ new Map();
28
+ groupDropCallbacks = /* @__PURE__ */ new Map();
29
+ bulkChangeCallbacks = /* @__PURE__ */ new Map();
30
+ entities = /* @__PURE__ */ new Map();
31
+ links = /* @__PURE__ */ new Map();
32
+ sockets = /* @__PURE__ */ new Map();
33
+ groups = /* @__PURE__ */ new Map();
34
+ quadTree = new QuadTree(new Rect(-1e5, -1e5, 2e5, 2e5));
35
+ history = new HistoryManager();
36
+ isApplyingHistory = false;
37
+ currentBatch = null;
38
+ currentUndoBatch = null;
39
+ isBatchingQuadTree = false;
40
+ getNextEid() {
41
+ return this.freeEids.pop() ?? this.eid++;
42
+ }
43
+ getNextLid() {
44
+ return this.freeLids.pop() ?? this.lid++;
45
+ }
46
+ getNextSid() {
47
+ return this.freeSids.pop() ?? this.sid++;
48
+ }
49
+ getNextGid() {
50
+ return this.freeGids.pop() ?? this.gid++;
51
+ }
52
+ getNextCallbackHandle() {
53
+ return this.freeCallbackIds.pop() ?? this.callbackIds++;
54
+ }
55
+ setupEntity(entity) {
56
+ entity.onMove((pos) => {
57
+ this.updateQuadTree();
58
+ for (const cb of this.entityMoveCallbacks.values()) try {
59
+ cb(entity, this.getWorldPosition(entity.id));
60
+ } catch (err) {
61
+ console.error(err);
62
+ }
63
+ });
64
+ }
65
+ notifyBulkChange() {
66
+ for (const cb of this.bulkChangeCallbacks.values()) try {
67
+ cb();
68
+ } catch (err) {
69
+ console.error(err);
70
+ }
71
+ }
72
+ registerBulkChangeListener(cb) {
73
+ const handle = this.getNextCallbackHandle();
74
+ this.bulkChangeCallbacks.set(handle, cb);
75
+ return handle;
76
+ }
77
+ setSocketValue(socketId, value) {
78
+ const socket = this.sockets.get(socketId);
79
+ if (!socket) return;
80
+ socket.value = value;
81
+ for (const cb of this.socketValueCallbacks.values()) cb(socket, value);
82
+ if (socket.kind === SocketKind.OUTPUT) {
83
+ for (const link of this.links.values()) if (link.from === socketId) this.setSocketValue(link.to, value);
84
+ }
85
+ }
86
+ registerSocketValueListener(cb) {
87
+ const handle = this.getNextCallbackHandle();
88
+ this.socketValueCallbacks.set(handle, cb);
89
+ return handle;
90
+ }
91
+ record(doActions, undoActions, label) {
92
+ if (this.isApplyingHistory) return;
93
+ if (this.currentBatch) return;
94
+ const das = Array.isArray(doActions) ? doActions : [doActions];
95
+ const uas = Array.isArray(undoActions) ? undoActions : [undoActions];
96
+ this.history.push({
97
+ do: das,
98
+ undo: uas,
99
+ label: label ?? "Action",
100
+ timestamp: Date.now()
101
+ });
102
+ }
103
+ batch(fn, label) {
104
+ if (this.currentBatch) {
105
+ fn();
106
+ return;
107
+ }
108
+ const oldApplying = this.isApplyingHistory;
109
+ const oldBatchingQT = this.isBatchingQuadTree;
110
+ const beforeState = this.toJSON();
111
+ try {
112
+ this.isApplyingHistory = true;
113
+ this.isBatchingQuadTree = true;
114
+ fn();
115
+ this.isBatchingQuadTree = oldBatchingQT;
116
+ this.updateQuadTree();
117
+ const afterState = this.toJSON();
118
+ this.history.push({
119
+ do: [{
120
+ type: "FROM_JSON",
121
+ data: afterState
122
+ }],
123
+ undo: [{
124
+ type: "FROM_JSON",
125
+ data: beforeState
126
+ }],
127
+ label: label ?? "Batch Action",
128
+ timestamp: Date.now()
129
+ });
130
+ } finally {
131
+ this.isApplyingHistory = oldApplying;
132
+ this.isBatchingQuadTree = oldBatchingQT;
133
+ }
134
+ }
135
+ undo() {
136
+ const cmd = this.history.undoStack.pop();
137
+ if (!cmd) return;
138
+ this.isApplyingHistory = true;
139
+ this.isBatchingQuadTree = true;
140
+ try {
141
+ for (let i = cmd.undo.length - 1; i >= 0; i--) this.applyAction(cmd.undo[i]);
142
+ this.isBatchingQuadTree = false;
143
+ this.updateQuadTree();
144
+ this.notifyBulkChange();
145
+ this.history.redoStack.push(cmd);
146
+ } finally {
147
+ this.isApplyingHistory = false;
148
+ this.isBatchingQuadTree = false;
149
+ }
150
+ }
151
+ redo() {
152
+ const cmd = this.history.redoStack.pop();
153
+ if (!cmd) return;
154
+ this.isApplyingHistory = true;
155
+ this.isBatchingQuadTree = true;
156
+ try {
157
+ for (const action of cmd.do) this.applyAction(action);
158
+ this.isBatchingQuadTree = false;
159
+ this.updateQuadTree();
160
+ this.notifyBulkChange();
161
+ this.history.undoStack.push(cmd);
162
+ } finally {
163
+ this.isApplyingHistory = false;
164
+ this.isBatchingQuadTree = false;
165
+ }
166
+ }
167
+ applyAction(action) {
168
+ switch (action.type) {
169
+ case "FROM_JSON":
170
+ this.fromJSON(action.data);
171
+ break;
172
+ case "MOVE_ENTITY": {
173
+ const entity = this.entities.get(action.id);
174
+ if (entity) {
175
+ entity.position.set(action.to.x, action.to.y);
176
+ for (const cb of this.entityMoveCallbacks.values()) cb(entity, this.getWorldPosition(entity.id));
177
+ this.updateQuadTree();
178
+ }
179
+ break;
180
+ }
181
+ case "MOVE_GROUP": {
182
+ const group = this.groups.get(action.id);
183
+ if (group) {
184
+ const dx = action.to.x - action.from.x;
185
+ const dy = action.to.y - action.from.y;
186
+ const oldApplying = this.isApplyingHistory;
187
+ this.isApplyingHistory = true;
188
+ try {
189
+ this.moveGroup(group, dx, dy);
190
+ } finally {
191
+ this.isApplyingHistory = oldApplying;
192
+ }
193
+ }
194
+ break;
195
+ }
196
+ case "CREATE_ENTITY": {
197
+ const entity = new Entity(action.id, action.inner);
198
+ entity.position.set(action.position.x, action.position.y);
199
+ this.entities.set(entity.id, entity);
200
+ this.eid = Math.max(this.eid, entity.id + 1);
201
+ this.setupEntity(entity);
202
+ if (action.parentId !== null) this.addToGroup(action.parentId, entity.id);
203
+ for (const cb of this.entityCreateCallbacks.values()) cb(entity);
204
+ this.updateQuadTree();
205
+ break;
206
+ }
207
+ case "DROP_ENTITY": {
208
+ const entity = this.entities.get(action.id);
209
+ if (entity) {
210
+ const oldApplying = this.isApplyingHistory;
211
+ this.isApplyingHistory = true;
212
+ try {
213
+ this.dropEntity(entity);
214
+ } finally {
215
+ this.isApplyingHistory = oldApplying;
216
+ }
217
+ }
218
+ break;
219
+ }
220
+ case "CREATE_LINK": {
221
+ const from = this.sockets.get(action.from);
222
+ const to = this.sockets.get(action.to);
223
+ if (from && to) {
224
+ const link = new Link(action.id, from.id, to.id, action.kind, action.inner);
225
+ this.links.set(link.id, link);
226
+ this.lid = Math.max(this.lid, link.id + 1);
227
+ for (const cb of this.linkCreateCallbacks.values()) cb(link);
228
+ }
229
+ break;
230
+ }
231
+ case "DROP_LINK": {
232
+ const link = this.links.get(action.id);
233
+ if (link) {
234
+ const oldApplying = this.isApplyingHistory;
235
+ this.isApplyingHistory = true;
236
+ try {
237
+ this.dropLink(link);
238
+ } finally {
239
+ this.isApplyingHistory = oldApplying;
240
+ }
241
+ }
242
+ break;
243
+ }
244
+ case "UPDATE_LINK": {
245
+ const link = this.links.get(action.id);
246
+ if (link) {
247
+ link.from = action.from.new;
248
+ link.to = action.to.new;
249
+ if (action.waypoints) link.waypoints = action.waypoints.new.map((p) => new Vec2(p.x, p.y));
250
+ for (const cb of this.linkUpdateCallbacks.values()) cb(link);
251
+ }
252
+ break;
253
+ }
254
+ case "ADD_TO_GROUP":
255
+ this.addToGroup(action.groupId, action.entityId);
256
+ break;
257
+ case "REMOVE_FROM_GROUP":
258
+ this.removeFromGroup(action.groupId, action.entityId);
259
+ break;
260
+ case "CREATE_SOCKET": {
261
+ const entity = this.entities.get(action.entityId);
262
+ if (entity) {
263
+ const socket = new Socket(action.id, entity.id, action.kind, action.name);
264
+ socket.offset.set(action.offset.x, action.offset.y);
265
+ this.sockets.set(socket.id, socket);
266
+ entity.sockets.set(socket.id, socket);
267
+ this.sid = Math.max(this.sid, socket.id + 1);
268
+ for (const cb of this.socketCreateCallbacks.values()) cb(socket);
269
+ }
270
+ break;
271
+ }
272
+ case "DROP_SOCKET": {
273
+ const socket = this.sockets.get(action.id);
274
+ if (socket) {
275
+ const oldApplying = this.isApplyingHistory;
276
+ this.isApplyingHistory = true;
277
+ try {
278
+ this.dropSocket(socket);
279
+ } finally {
280
+ this.isApplyingHistory = oldApplying;
281
+ }
282
+ }
283
+ break;
284
+ }
285
+ }
286
+ }
287
+ registerEntityCreateListener(cb) {
288
+ const handle = this.getNextCallbackHandle();
289
+ this.entityCreateCallbacks.set(handle, cb);
290
+ return handle;
291
+ }
292
+ registerEntityDropListener(cb) {
293
+ const handle = this.getNextCallbackHandle();
294
+ this.entityDropCallbacks.set(handle, cb);
295
+ return handle;
296
+ }
297
+ registerEntityMoveListener(cb) {
298
+ const handle = this.getNextCallbackHandle();
299
+ this.entityMoveCallbacks.set(handle, cb);
300
+ return handle;
301
+ }
302
+ registerSocketMoveListener(cb) {
303
+ const handle = this.getNextCallbackHandle();
304
+ this.socketMoveCallbacks.set(handle, cb);
305
+ return handle;
306
+ }
307
+ notifySocketMove(socket) {
308
+ for (const cb of this.socketMoveCallbacks.values()) try {
309
+ cb(socket);
310
+ } catch (err) {
311
+ console.error(err);
312
+ }
313
+ }
314
+ registerLinkCreateListener(cb) {
315
+ const handle = this.getNextCallbackHandle();
316
+ this.linkCreateCallbacks.set(handle, cb);
317
+ return handle;
318
+ }
319
+ registerLinkDropListener(cb) {
320
+ const handle = this.getNextCallbackHandle();
321
+ this.linkDropCallbacks.set(handle, cb);
322
+ return handle;
323
+ }
324
+ registerLinkUpdateListener(cb) {
325
+ const handle = this.getNextCallbackHandle();
326
+ this.linkUpdateCallbacks.set(handle, cb);
327
+ return handle;
328
+ }
329
+ registerSocketCreateListener(cb) {
330
+ const handle = this.getNextCallbackHandle();
331
+ this.socketCreateCallbacks.set(handle, cb);
332
+ return handle;
333
+ }
334
+ registerSocketDropListener(cb) {
335
+ const handle = this.getNextCallbackHandle();
336
+ this.socketDropCallbacks.set(handle, cb);
337
+ return handle;
338
+ }
339
+ registerGroupCreateListener(cb) {
340
+ const handle = this.getNextCallbackHandle();
341
+ this.groupCreateCallbacks.set(handle, cb);
342
+ return handle;
343
+ }
344
+ registerGroupDropListener(cb) {
345
+ const handle = this.getNextCallbackHandle();
346
+ this.groupDropCallbacks.set(handle, cb);
347
+ return handle;
348
+ }
349
+ unregisterListener(handle) {
350
+ if (this.linkCreateCallbacks.delete(handle) || this.linkDropCallbacks.delete(handle) || this.linkUpdateCallbacks.delete(handle) || this.entityCreateCallbacks.delete(handle) || this.entityDropCallbacks.delete(handle) || this.entityMoveCallbacks.delete(handle) || this.socketCreateCallbacks.delete(handle) || this.socketDropCallbacks.delete(handle) || this.socketMoveCallbacks.delete(handle) || this.groupCreateCallbacks.delete(handle) || this.groupDropCallbacks.delete(handle) || this.bulkChangeCallbacks.delete(handle)) {
351
+ this.freeCallbackIds.push(handle);
352
+ return true;
353
+ }
354
+ return false;
355
+ }
356
+ newGroup(name = "") {
357
+ const group = new Group(this.getNextGid(), name);
358
+ this.groups.set(group.id, group);
359
+ for (const cb of this.groupCreateCallbacks.values()) try {
360
+ cb(group);
361
+ } catch (err) {
362
+ console.error(err);
363
+ }
364
+ return group;
365
+ }
366
+ dropGroup(group) {
367
+ if (this.groups.delete(group.id)) {
368
+ if (group.parentId !== null) this.removeGroupFromGroup(group.parentId, group.id);
369
+ for (const eid of group.entities) {
370
+ const entity = this.entities.get(eid);
371
+ if (entity) entity.parentId = null;
372
+ }
373
+ for (const gid of group.groups) {
374
+ const childGroup = this.groups.get(gid);
375
+ if (childGroup) childGroup.parentId = null;
376
+ }
377
+ this.freeGids.push(group.id);
378
+ for (const cb of this.groupDropCallbacks.values()) try {
379
+ cb(group);
380
+ } catch (err) {
381
+ console.error(err);
382
+ }
383
+ this.updateQuadTree();
384
+ }
385
+ }
386
+ getWorldPosition(entityId) {
387
+ const entity = this.entities.get(entityId);
388
+ if (!entity) return new Vec2();
389
+ const pos = entity.position.clone();
390
+ let currentParentId = entity.parentId;
391
+ while (currentParentId !== null) {
392
+ const parent = this.groups.get(currentParentId);
393
+ if (!parent) break;
394
+ pos.x += parent.position.x;
395
+ pos.y += parent.position.y;
396
+ currentParentId = parent.parentId;
397
+ }
398
+ return pos;
399
+ }
400
+ getGroupWorldPosition(groupId) {
401
+ const group = this.groups.get(groupId);
402
+ if (!group) return new Vec2();
403
+ const pos = group.position.clone();
404
+ let currentParentId = group.parentId;
405
+ while (currentParentId !== null) {
406
+ const parent = this.groups.get(currentParentId);
407
+ if (!parent) break;
408
+ pos.x += parent.position.x;
409
+ pos.y += parent.position.y;
410
+ currentParentId = parent.parentId;
411
+ }
412
+ return pos;
413
+ }
414
+ moveGroup(group, dx, dy) {
415
+ const oldBatching = this.isBatchingQuadTree;
416
+ this.isBatchingQuadTree = true;
417
+ group.position.x += dx;
418
+ group.position.y += dy;
419
+ const notifyRecursive = (g) => {
420
+ for (const eid of g.entities) {
421
+ const entity = this.entities.get(eid);
422
+ if (entity) for (const cb of this.entityMoveCallbacks.values()) cb(entity, this.getWorldPosition(eid));
423
+ }
424
+ for (const gid of g.groups) {
425
+ const childGroup = this.groups.get(gid);
426
+ if (childGroup) notifyRecursive(childGroup);
427
+ }
428
+ };
429
+ try {
430
+ notifyRecursive(group);
431
+ } finally {
432
+ this.isBatchingQuadTree = oldBatching;
433
+ this.updateQuadTree();
434
+ }
435
+ }
436
+ addToGroup(groupId, entityId) {
437
+ const group = this.groups.get(groupId);
438
+ const entity = this.entities.get(entityId);
439
+ if (group && entity) {
440
+ if (entity.parentId !== null) this.removeFromGroup(entity.parentId, entityId);
441
+ group.add(entityId);
442
+ entity.parentId = groupId;
443
+ this.updateQuadTree();
444
+ }
445
+ }
446
+ removeFromGroup(groupId, entityId) {
447
+ const group = this.groups.get(groupId);
448
+ const entity = this.entities.get(entityId);
449
+ if (group && entity) {
450
+ group.remove(entityId);
451
+ entity.parentId = null;
452
+ this.updateQuadTree();
453
+ }
454
+ }
455
+ addGroupToGroup(parentGroupId, childGroupId) {
456
+ const parent = this.groups.get(parentGroupId);
457
+ const child = this.groups.get(childGroupId);
458
+ if (parent && child && parentGroupId !== childGroupId) {
459
+ if (child.parentId !== null) this.removeGroupFromGroup(child.parentId, childGroupId);
460
+ parent.addGroup(childGroupId);
461
+ child.parentId = parentGroupId;
462
+ this.updateQuadTree();
463
+ }
464
+ }
465
+ removeGroupFromGroup(parentGroupId, childGroupId) {
466
+ const parent = this.groups.get(parentGroupId);
467
+ const child = this.groups.get(childGroupId);
468
+ if (parent && child) {
469
+ parent.removeGroup(childGroupId);
470
+ child.parentId = null;
471
+ this.updateQuadTree();
472
+ }
473
+ }
474
+ updateQuadTree() {
475
+ if (this.isBatchingQuadTree) return;
476
+ this.quadTree.clear();
477
+ for (const entity of this.entities.values()) {
478
+ const entityWorldPos = this.getWorldPosition(entity.id);
479
+ this.quadTree.insert(entityWorldPos, entity.id);
480
+ for (const socket of entity.sockets.values()) this.quadTree.insert(new Vec2(entityWorldPos.x + socket.offset.x, entityWorldPos.y + socket.offset.y), entity.id);
481
+ }
482
+ }
483
+ newEntity(inner, forcedId) {
484
+ const ett = new Entity(forcedId ?? this.getNextEid(), inner);
485
+ this.entities.set(ett.id, ett);
486
+ if (forcedId !== void 0) this.eid = Math.max(this.eid, forcedId + 1);
487
+ this.setupEntity(ett);
488
+ if (!this.isApplyingHistory) this.record({
489
+ type: "CREATE_ENTITY",
490
+ id: ett.id,
491
+ inner: ett.inner,
492
+ position: { ...ett.position },
493
+ parentId: ett.parentId
494
+ }, {
495
+ type: "DROP_ENTITY",
496
+ id: ett.id,
497
+ inner: ett.inner,
498
+ position: { ...ett.position },
499
+ parentId: ett.parentId
500
+ }, "Create Entity");
501
+ for (const cb of this.entityCreateCallbacks.values()) try {
502
+ cb(ett);
503
+ } catch (err) {
504
+ console.error(err);
505
+ }
506
+ this.updateQuadTree();
507
+ return ett;
508
+ }
509
+ dropEntity(entity) {
510
+ if (this.entities.has(entity.id)) this.batch(() => {
511
+ this.record({
512
+ type: "DROP_ENTITY",
513
+ id: entity.id,
514
+ inner: entity.inner,
515
+ position: { ...entity.position },
516
+ parentId: entity.parentId
517
+ }, {
518
+ type: "CREATE_ENTITY",
519
+ id: entity.id,
520
+ inner: entity.inner,
521
+ position: { ...entity.position },
522
+ parentId: entity.parentId
523
+ }, "Drop Entity");
524
+ if (entity.parentId !== null) this.removeFromGroup(entity.parentId, entity.id);
525
+ this.entities.delete(entity.id);
526
+ for (const socket of entity.sockets.values()) this.dropSocket(socket);
527
+ this.freeEids.push(entity.id);
528
+ for (const cb of this.entityDropCallbacks.values()) try {
529
+ cb(entity);
530
+ } catch (err) {
531
+ console.error(err);
532
+ }
533
+ this.updateQuadTree();
534
+ }, "Drop Entity");
535
+ }
536
+ newSocket(entity, kind, name = "", forcedId) {
537
+ const socket = new Socket(forcedId ?? this.getNextSid(), entity.id, kind, name);
538
+ this.sockets.set(socket.id, socket);
539
+ entity.sockets.set(socket.id, socket);
540
+ if (forcedId !== void 0) this.sid = Math.max(this.sid, forcedId + 1);
541
+ if (!this.isApplyingHistory) this.record({
542
+ type: "CREATE_SOCKET",
543
+ id: socket.id,
544
+ entityId: entity.id,
545
+ kind,
546
+ name,
547
+ offset: { ...socket.offset }
548
+ }, {
549
+ type: "DROP_SOCKET",
550
+ id: socket.id,
551
+ entityId: entity.id,
552
+ kind,
553
+ name,
554
+ offset: { ...socket.offset }
555
+ }, "Create Socket");
556
+ for (const cb of this.socketCreateCallbacks.values()) try {
557
+ cb(socket);
558
+ } catch (err) {
559
+ console.error(err);
560
+ }
561
+ return socket;
562
+ }
563
+ dropSocket(socket) {
564
+ if (this.sockets.delete(socket.id)) {
565
+ if (!this.isApplyingHistory) this.record({
566
+ type: "DROP_SOCKET",
567
+ id: socket.id,
568
+ entityId: socket.entityId,
569
+ kind: socket.kind,
570
+ name: socket.name,
571
+ offset: { ...socket.offset }
572
+ }, {
573
+ type: "CREATE_SOCKET",
574
+ id: socket.id,
575
+ entityId: socket.entityId,
576
+ kind: socket.kind,
577
+ name: socket.name,
578
+ offset: { ...socket.offset }
579
+ }, "Drop Socket");
580
+ const entity = this.entities.get(socket.entityId);
581
+ if (entity) entity.sockets.delete(socket.id);
582
+ for (const link of this.links.values()) if (link.from === socket.id || link.to === socket.id) this.dropLink(link);
583
+ this.freeSids.push(socket.id);
584
+ for (const cb of this.socketDropCallbacks.values()) try {
585
+ cb(socket);
586
+ } catch (err) {
587
+ console.error(err);
588
+ }
589
+ }
590
+ }
591
+ newLink(from, to, kind = LinkKind.BEZIER, forcedId, inner = {}) {
592
+ if (!this.canLink(from, to)) return null;
593
+ const link = new Link(forcedId ?? this.getNextLid(), from.id, to.id, kind, inner);
594
+ this.links.set(link.id, link);
595
+ if (forcedId !== void 0) this.lid = Math.max(this.lid, forcedId + 1);
596
+ if (!this.isApplyingHistory) this.record({
597
+ type: "CREATE_LINK",
598
+ id: link.id,
599
+ from: link.from,
600
+ to: link.to,
601
+ kind: link.kind,
602
+ inner: link.inner
603
+ }, {
604
+ type: "DROP_LINK",
605
+ id: link.id,
606
+ from: link.from,
607
+ to: link.to,
608
+ kind: link.kind,
609
+ inner: link.inner
610
+ }, "Create Link");
611
+ if (from.value !== null) this.setSocketValue(to.id, from.value);
612
+ for (const cb of this.linkCreateCallbacks.values()) try {
613
+ cb(link);
614
+ } catch (err) {
615
+ console.error(err);
616
+ }
617
+ return link;
618
+ }
619
+ updateLink(link, fromId, toId) {
620
+ const oldFrom = link.from;
621
+ const oldTo = link.to;
622
+ const newFrom = fromId ?? oldFrom;
623
+ const newTo = toId ?? oldTo;
624
+ if (oldFrom === newFrom && oldTo === newTo) return;
625
+ const fromSocket = this.sockets.get(newFrom);
626
+ const toSocket = this.sockets.get(newTo);
627
+ if (!fromSocket || !toSocket || !this.canLink(fromSocket, toSocket)) return;
628
+ const oldWaypoints = link.waypoints.map((p) => ({
629
+ x: p.x,
630
+ y: p.y
631
+ }));
632
+ if (!this.isApplyingHistory) this.record({
633
+ type: "UPDATE_LINK",
634
+ id: link.id,
635
+ from: {
636
+ old: oldFrom,
637
+ new: newFrom
638
+ },
639
+ to: {
640
+ old: oldTo,
641
+ new: newTo
642
+ },
643
+ waypoints: {
644
+ old: oldWaypoints,
645
+ new: oldWaypoints
646
+ }
647
+ }, {
648
+ type: "UPDATE_LINK",
649
+ id: link.id,
650
+ from: {
651
+ old: newFrom,
652
+ new: oldFrom
653
+ },
654
+ to: {
655
+ old: newTo,
656
+ new: oldTo
657
+ },
658
+ waypoints: {
659
+ old: oldWaypoints,
660
+ new: oldWaypoints
661
+ }
662
+ }, "Update Link");
663
+ link.from = newFrom;
664
+ link.to = newTo;
665
+ for (const cb of this.linkUpdateCallbacks.values()) try {
666
+ cb(link);
667
+ } catch (err) {
668
+ console.error(err);
669
+ }
670
+ }
671
+ setLinkWaypoints(link, waypoints) {
672
+ const oldWaypoints = link.waypoints.map((p) => ({
673
+ x: p.x,
674
+ y: p.y
675
+ }));
676
+ const newWaypoints = waypoints.map((p) => ({
677
+ x: p.x,
678
+ y: p.y
679
+ }));
680
+ if (!this.isApplyingHistory) this.record({
681
+ type: "UPDATE_LINK",
682
+ id: link.id,
683
+ from: {
684
+ old: link.from,
685
+ new: link.from
686
+ },
687
+ to: {
688
+ old: link.to,
689
+ new: link.to
690
+ },
691
+ waypoints: {
692
+ old: oldWaypoints,
693
+ new: newWaypoints
694
+ }
695
+ }, {
696
+ type: "UPDATE_LINK",
697
+ id: link.id,
698
+ from: {
699
+ old: link.from,
700
+ new: link.from
701
+ },
702
+ to: {
703
+ old: link.to,
704
+ new: link.to
705
+ },
706
+ waypoints: {
707
+ old: newWaypoints,
708
+ new: oldWaypoints
709
+ }
710
+ }, "Update Link Routing");
711
+ link.waypoints = waypoints.map((p) => p.clone());
712
+ for (const cb of this.linkUpdateCallbacks.values()) try {
713
+ cb(link);
714
+ } catch (err) {
715
+ console.error(err);
716
+ }
717
+ }
718
+ canLink(from, to) {
719
+ if (from.id === to.id) return false;
720
+ if (from.entityId === to.entityId) return false;
721
+ if (from.kind === to.kind) return false;
722
+ for (const link of this.links.values()) if (link.from === from.id && link.to === to.id || link.from === to.id && link.to === from.id) return false;
723
+ if (this.detectCycle(from, to)) return false;
724
+ return true;
725
+ }
726
+ detectCycle(from, to) {
727
+ const visited = /* @__PURE__ */ new Set();
728
+ const stack = [to.entityId];
729
+ while (stack.length > 0) {
730
+ const currentEntityId = stack.pop();
731
+ if (currentEntityId === from.entityId) return true;
732
+ if (visited.has(currentEntityId)) continue;
733
+ visited.add(currentEntityId);
734
+ const entity = this.entities.get(currentEntityId);
735
+ if (!entity) continue;
736
+ for (const socket of entity.sockets.values()) if (socket.kind === SocketKind.OUTPUT) {
737
+ for (const link of this.links.values()) if (link.from === socket.id) {
738
+ const targetSocket = this.sockets.get(link.to);
739
+ if (targetSocket) stack.push(targetSocket.entityId);
740
+ }
741
+ }
742
+ }
743
+ return false;
744
+ }
745
+ dropLink(link) {
746
+ if (this.links.delete(link.id)) {
747
+ if (!this.isApplyingHistory) this.record({
748
+ type: "DROP_LINK",
749
+ id: link.id,
750
+ from: link.from,
751
+ to: link.to,
752
+ kind: link.kind,
753
+ inner: link.inner
754
+ }, {
755
+ type: "CREATE_LINK",
756
+ id: link.id,
757
+ from: link.from,
758
+ to: link.to,
759
+ kind: link.kind,
760
+ inner: link.inner
761
+ }, "Drop Link");
762
+ this.freeLids.push(link.id);
763
+ for (const cb of this.linkDropCallbacks.values()) try {
764
+ cb(link);
765
+ } catch (err) {
766
+ console.error(err);
767
+ }
768
+ }
769
+ }
770
+ toJSON() {
771
+ return {
772
+ entities: Array.from(this.entities.values()).map((e) => ({
773
+ id: e.id,
774
+ position: {
775
+ x: e.position.x,
776
+ y: e.position.y
777
+ },
778
+ inner: e.inner,
779
+ parentId: e.parentId,
780
+ sockets: Array.from(e.sockets.values()).map((s) => ({
781
+ id: s.id,
782
+ kind: s.kind,
783
+ name: s.name,
784
+ offset: {
785
+ x: s.offset.x,
786
+ y: s.offset.y
787
+ }
788
+ }))
789
+ })),
790
+ links: Array.from(this.links.values()).map((l) => ({
791
+ id: l.id,
792
+ from: l.from,
793
+ to: l.to,
794
+ kind: l.kind,
795
+ waypoints: l.waypoints.map((p) => ({
796
+ x: p.x,
797
+ y: p.y
798
+ })),
799
+ inner: l.inner
800
+ })),
801
+ groups: Array.from(this.groups.values()).map((g) => ({
802
+ id: g.id,
803
+ name: g.name,
804
+ entities: Array.from(g.entities),
805
+ groups: Array.from(g.groups),
806
+ position: {
807
+ x: g.position.x,
808
+ y: g.position.y
809
+ },
810
+ parentId: g.parentId
811
+ }))
812
+ };
813
+ }
814
+ fromJSON(data) {
815
+ this.entities.clear();
816
+ this.links.clear();
817
+ this.sockets.clear();
818
+ this.groups.clear();
819
+ this.eid = 0;
820
+ this.lid = 0;
821
+ this.sid = 0;
822
+ this.gid = 0;
823
+ this.freeEids = [];
824
+ this.freeLids = [];
825
+ this.freeSids = [];
826
+ this.freeGids = [];
827
+ for (const eData of data.entities) {
828
+ const entity = new Entity(eData.id, eData.inner);
829
+ entity.position.set(eData.position.x, eData.position.y);
830
+ entity.parentId = eData.parentId;
831
+ this.entities.set(entity.id, entity);
832
+ this.eid = Math.max(this.eid, entity.id + 1);
833
+ this.setupEntity(entity);
834
+ for (const sData of eData.sockets) {
835
+ const socket = new Socket(sData.id, entity.id, sData.kind, sData.name);
836
+ socket.offset.set(sData.offset.x, sData.offset.y);
837
+ this.sockets.set(socket.id, socket);
838
+ entity.sockets.set(socket.id, socket);
839
+ this.sid = Math.max(this.sid, socket.id + 1);
840
+ }
841
+ }
842
+ for (const lData of data.links) {
843
+ const link = new Link(lData.id, lData.from, lData.to, lData.kind, lData.inner);
844
+ if (lData.waypoints) link.waypoints = lData.waypoints.map((p) => new Vec2(p.x, p.y));
845
+ this.links.set(link.id, link);
846
+ this.lid = Math.max(this.lid, link.id + 1);
847
+ }
848
+ if (data.groups) for (const gData of data.groups) {
849
+ const group = new Group(gData.id, gData.name);
850
+ group.position.set(gData.position.x, gData.position.y);
851
+ group.parentId = gData.parentId;
852
+ for (const eid of gData.entities) group.add(eid);
853
+ if (gData.groups) for (const gid of gData.groups) group.addGroup(gid);
854
+ this.groups.set(group.id, group);
855
+ this.gid = Math.max(this.gid, group.id + 1);
856
+ }
857
+ this.updateQuadTree();
858
+ }
859
+ };
860
+
861
+ //#endregion
862
+ export { Context };
863
+ //# sourceMappingURL=context.js.map