Add serialisation support for floating links (#771)

Adds the serialisation code to support the new floating links impl.
This commit is contained in:
filtered
2025-03-14 02:20:14 +11:00
committed by GitHub
parent df36b23db8
commit 18811f50fc
5 changed files with 107 additions and 25 deletions

View File

@@ -138,6 +138,14 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
return
}
/** Internal only. Not required for serialisation; calculated on deserialise. */
#lastFloatingLinkId: number = 0
#floatingLinks: Map<LinkId, LLink> = new Map()
get floatingLinks(): ReadonlyMap<LinkId, LLink> {
return this.#floatingLinks
}
#reroutes = new Map<RerouteId, Reroute>()
/** All reroutes in this graph. */
public get reroutes(): Map<RerouteId, Reroute> {
@@ -250,6 +258,7 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
this._links.clear()
this.reroutes.clear()
this.#floatingLinks.clear()
// other scene stuff
this._groups = []
@@ -1369,12 +1378,12 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
* Creates the object if it does not exist.
* @param serialisedReroute See {@link SerialisableReroute}
*/
setReroute({ id, parentId, pos, linkIds }: OptionalProps<SerialisableReroute, "id">): Reroute {
setReroute({ id, parentId, pos, linkIds, floating }: OptionalProps<SerialisableReroute, "id">): Reroute {
id ??= ++this.state.lastRerouteId
if (id > this.state.lastRerouteId) this.state.lastRerouteId = id
const reroute = this.reroutes.get(id) ?? new Reroute(id, this)
reroute.update(parentId, pos, linkIds)
reroute.update(parentId, pos, linkIds, floating)
this.reroutes.set(id, reroute)
return reroute
}
@@ -1417,7 +1426,7 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
if (!reroute) return
// Extract reroute from the reroute chain
const { parentId, linkIds } = reroute
const { parentId, linkIds, floatingLinkIds } = reroute
for (const reroute of reroutes.values()) {
if (reroute.parentId === id) reroute.parentId = parentId
}
@@ -1427,6 +1436,18 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
if (link && link.parentId === id) link.parentId = parentId
}
for (const linkId of floatingLinkIds) {
const link = this.floatingLinks.get(linkId)
if (!link) {
console.warn(`Removed reroute had floating link ID that did not exist [${linkId}]`)
continue
}
// Remove floating links with no reroutes
if (parentId === undefined) this.#floatingLinks.delete(linkId)
else if (link.parentId === id) link.parentId = parentId
}
reroutes.delete(id)
this.setDirtyCanvas(false, true)
}
@@ -1450,7 +1471,7 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
* @returns value of the node
*/
serialize(option?: { sortNodes: boolean }): ISerialisedGraph {
const { config, state, groups, nodes, reroutes, extra } = this.asSerialisable(option)
const { config, state, groups, nodes, reroutes, extra, floatingLinks } = this.asSerialisable(option)
const linkArray = [...this._links.values()]
const links = linkArray.map(x => x.serialize())
@@ -1467,6 +1488,7 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
last_link_id: state.lastLinkId,
nodes,
links,
floatingLinks,
groups,
config,
extra,
@@ -1494,6 +1516,7 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
const groups = this._groups.map(x => x.serialize())
const links = this._links.size ? [...this._links.values()].map(x => x.asSerialisable()) : undefined
const floatingLinks = this.floatingLinks.size ? [...this.floatingLinks.values()].map(x => x.asSerialisable()) : undefined
const reroutes = this.reroutes.size ? [...this.reroutes.values()].map(x => x.asSerialisable()) : undefined
const data: ReturnType<typeof this.asSerialisable> = {
@@ -1503,6 +1526,7 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
groups,
nodes,
links,
floatingLinks,
reroutes,
extra,
}
@@ -1574,14 +1598,36 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
reroutes = data.reroutes
}
// Floating links
if (Array.isArray(data.floatingLinks)) {
for (const linkData of data.floatingLinks) {
const floatingLink = LLink.create(linkData)
this.#floatingLinks.set(floatingLink.id, floatingLink)
if (floatingLink.id > this.#lastFloatingLinkId) this.#lastFloatingLinkId = floatingLink.id
}
}
// Reroutes
if (Array.isArray(reroutes)) {
for (const rerouteData of reroutes) {
const reroute = this.setReroute(rerouteData)
this.setReroute(rerouteData)
}
}
// Drop broken links, and ignore reroutes with no valid links
if (!reroute.validateLinks(this._links))
this.reroutes.delete(rerouteData.id)
// Cache floating link IDs on reroutes
for (const floatingLink of this.floatingLinks.values()) {
const reroutes = LLink.getReroutes(this, floatingLink)
for (const reroute of reroutes) {
reroute.floatingLinkIds.add(floatingLink.id)
}
}
// Drop broken reroutes
for (const reroute of this.reroutes.values()) {
// Drop broken links, and ignore reroutes with no valid links
if (!reroute.validateLinks(this._links, this.floatingLinks)) {
this.reroutes.delete(reroute.id)
}
}
@@ -1590,13 +1636,7 @@ export class LGraph implements LinkNetwork, Serialisable<SerialisableGraph> {
// copy all stored fields
for (const i in data) {
// links must be accepted
if (
i == "nodes" ||
i == "groups" ||
i == "links" ||
i === "state" ||
i === "reroutes"
) {
if (["nodes", "groups", "links", "state", "reroutes", "floatingLinks"].includes(i)) {
continue
}
// @ts-expect-error #574 Legacy property assignment

View File

@@ -3157,10 +3157,12 @@ export class LGraphCanvas implements ConnectionColorContext {
// Remap linkIds
for (const reroute of reroutes.values()) {
const ids = [...reroute.linkIds].map(x => links.get(x)?.id ?? x)
reroute.update(reroute.parentId, undefined, ids)
reroute.update(reroute.parentId, undefined, ids, reroute.floating)
// Remove any invalid items
if (!reroute.validateLinks(graph.links)) graph.removeReroute(reroute.id)
if (!reroute.validateLinks(graph.links, graph.floatingLinks)) {
graph.removeReroute(reroute.id)
}
}
// Adjust positions

View File

@@ -15,6 +15,16 @@ import { distance } from "./measure"
export type RerouteId = number
/** The input or output slot that an incomplete reroute link is connected to. */
export interface FloatingRerouteSlot {
/** The ID of the node that the slot belongs to */
nodeId: NodeId
/** The index of the slot on the node */
slot: number
/** Floating connection to an input or output */
slotType: "input" | "output"
}
/**
* Represents an additional point on the graph that a link path will travel through. Used for visual organisation only.
*
@@ -42,6 +52,9 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
this.#parentId = value
}
/** Set when the reroute has no complete links but is still on the canvas. */
floating?: FloatingRerouteSlot
#pos = this.#malloc.subarray(0, 2)
/** @inheritdoc */
get pos(): Point {
@@ -68,6 +81,9 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
/** The ID ({@link LLink.id}) of every link using this reroute */
linkIds: Set<LinkId>
/** The ID ({@link LLink.id}) of every floating link using this reroute */
floatingLinkIds: Set<LinkId>
/** Cached cos */
cos: number = 0
sin: number = 0
@@ -132,10 +148,12 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
pos?: Point,
parentId?: RerouteId,
linkIds?: Iterable<LinkId>,
floatingLinkIds?: Iterable<LinkId>,
) {
this.#network = new WeakRef(network)
this.update(parentId, pos, linkIds)
this.linkIds ??= new Set()
this.floatingLinkIds = new Set(floatingLinkIds)
}
/**
@@ -150,10 +168,12 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
parentId: RerouteId | undefined,
pos?: Point,
linkIds?: Iterable<LinkId>,
floating?: FloatingRerouteSlot,
): void {
this.parentId = parentId
if (pos) this.pos = pos
if (linkIds) this.linkIds = new Set(linkIds)
this.floating = floating
}
/**
@@ -161,12 +181,15 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
* @param links Collection of valid links
* @returns true if any links remain after validation
*/
validateLinks(links: Map<LinkId, LLink>): boolean {
const { linkIds } = this
validateLinks(links: ReadonlyMap<LinkId, LLink>, floatingLinks: ReadonlyMap<LinkId, LLink>): boolean {
const { linkIds, floatingLinkIds } = this
for (const linkId of linkIds) {
if (!links.get(linkId)) linkIds.delete(linkId)
}
return linkIds.size > 0
for (const linkId of floatingLinkIds) {
if (!floatingLinks.get(linkId)) floatingLinkIds.delete(linkId)
}
return linkIds.size > 0 || floatingLinkIds.size > 0
}
/**
@@ -333,11 +356,24 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
/** @inheritdoc */
asSerialisable(): SerialisableReroute {
const { id, parentId, pos, linkIds } = this
const floating = floatingToSerialisable(this.floating)
return {
id: this.id,
parentId: this.parentId,
pos: [this.pos[0], this.pos[1]],
linkIds: [...this.linkIds],
id,
parentId,
pos: [pos[0], pos[1]],
linkIds: [...linkIds],
floating,
}
}
}
function floatingToSerialisable(floating: FloatingRerouteSlot | undefined): FloatingRerouteSlot | undefined {
return floating
? {
nodeId: floating.nodeId,
slot: floating.slot,
slotType: floating.slotType,
}
: undefined
}

View File

@@ -114,6 +114,7 @@ export interface IPinnable {
export interface ReadonlyLinkNetwork {
readonly links: ReadonlyMap<LinkId, LLink>
readonly reroutes: ReadonlyMap<RerouteId, Reroute>
readonly floatingLinks: ReadonlyMap<LinkId, LLink>
getNodeById(id: NodeId): LGraphNode | null
}

View File

@@ -12,7 +12,7 @@ import type { IGraphGroupFlags } from "../LGraphGroup"
import type { NodeId, NodeProperty } from "../LGraphNode"
import type { LiteGraph } from "../litegraph"
import type { LinkId, SerialisedLLinkArray } from "../LLink"
import type { RerouteId } from "../Reroute"
import type { FloatingRerouteSlot, RerouteId } from "../Reroute"
import type { TWidgetValue } from "../types/widgets"
import type { RenderShape } from "./globalEnums"
@@ -36,6 +36,7 @@ export interface SerialisableGraph {
groups?: ISerialisedGroup[]
nodes?: ISerialisedNode[]
links?: SerialisableLLink[]
floatingLinks?: SerialisableLLink[]
reroutes?: SerialisableReroute[]
extra?: Dictionary<unknown>
}
@@ -83,6 +84,7 @@ export interface ISerialisedGraph {
last_link_id: number
nodes: ISerialisedNode[]
links: SerialisedLLinkArray[]
floatingLinks?: SerialisableLLink[]
groups: ISerialisedGroup[]
config: LGraphConfig
version: typeof LiteGraph.VERSION
@@ -128,6 +130,7 @@ export interface SerialisableReroute {
parentId?: RerouteId
pos: Point
linkIds: LinkId[]
floating?: FloatingRerouteSlot
}
export interface SerialisableLLink {