Files
visualizador_instanciados/backend_go/hierarchy_layout_bridge.go
2026-03-23 11:13:27 -03:00

269 lines
7.3 KiB
Go

package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os/exec"
"strings"
)
const (
hierarchyGraphQueryID = "hierarchy"
rustHierarchyLayoutEngineID = "rust_radial_sugiyama"
)
type hierarchyLayoutResult struct {
Nodes []Node
Edges []Edge
RouteSegments []RouteSegment
}
type hierarchyLayoutPrepared struct {
Request hierarchyLayoutRequest
NormalizedEdges []Edge
}
type hierarchyLayoutRequest struct {
RootIRI string `json:"root_iri"`
Nodes []hierarchyLayoutRequestNode `json:"nodes"`
Edges []hierarchyLayoutRequestEdge `json:"edges"`
}
type hierarchyLayoutRequestNode struct {
NodeID uint32 `json:"node_id"`
IRI string `json:"iri"`
}
type hierarchyLayoutRequestEdge struct {
EdgeIndex int `json:"edge_index"`
ParentID uint32 `json:"parent_id"`
ChildID uint32 `json:"child_id"`
PredicateIRI *string `json:"predicate_iri,omitempty"`
}
type hierarchyLayoutResponse struct {
Nodes []hierarchyLayoutResponseNode `json:"nodes"`
RouteSegments []hierarchyLayoutResponseRouteSegment `json:"route_segments"`
}
type hierarchyLayoutResponseNode struct {
NodeID uint32 `json:"node_id"`
X float64 `json:"x"`
Y float64 `json:"y"`
Level int `json:"level"`
}
type hierarchyLayoutResponseRouteSegment struct {
EdgeIndex int `json:"edge_index"`
Kind string `json:"kind"`
Points []hierarchyLayoutResponseRoutePoint `json:"points"`
}
type hierarchyLayoutResponseRoutePoint struct {
X float64 `json:"x"`
Y float64 `json:"y"`
}
type hierarchyEdgeKey struct {
ParentID uint32
ChildID uint32
}
func shouldUseRustHierarchyLayout(cfg Config, graphQueryID string) bool {
return cfg.HierarchyLayoutEngine == "rust" && graphQueryID == hierarchyGraphQueryID
}
func prepareHierarchyLayoutRequest(
rootIRI string,
nodes []Node,
edges []Edge,
preds *PredicateDict,
) hierarchyLayoutPrepared {
requestNodes := make([]hierarchyLayoutRequestNode, 0, len(nodes))
for _, node := range nodes {
requestNodes = append(requestNodes, hierarchyLayoutRequestNode{
NodeID: node.ID,
IRI: node.IRI,
})
}
predicateIRIs := []string(nil)
if preds != nil {
predicateIRIs = preds.IRIs()
}
seenEdges := make(map[hierarchyEdgeKey]struct{}, len(edges))
normalizedEdges := make([]Edge, 0, len(edges))
requestEdges := make([]hierarchyLayoutRequestEdge, 0, len(edges))
for _, edge := range edges {
parentID := edge.Target
childID := edge.Source
if parentID == childID {
continue
}
key := hierarchyEdgeKey{ParentID: parentID, ChildID: childID}
if _, ok := seenEdges[key]; ok {
continue
}
seenEdges[key] = struct{}{}
normalizedEdges = append(normalizedEdges, edge)
var predicateIRI *string
if int(edge.PredicateID) >= 0 && int(edge.PredicateID) < len(predicateIRIs) {
value := predicateIRIs[edge.PredicateID]
if strings.TrimSpace(value) != "" {
predicateIRI = &value
}
}
requestEdges = append(requestEdges, hierarchyLayoutRequestEdge{
EdgeIndex: len(normalizedEdges) - 1,
ParentID: parentID,
ChildID: childID,
PredicateIRI: predicateIRI,
})
}
return hierarchyLayoutPrepared{
Request: hierarchyLayoutRequest{
RootIRI: rootIRI,
Nodes: requestNodes,
Edges: requestEdges,
},
NormalizedEdges: normalizedEdges,
}
}
func applyHierarchyLayoutResponse(
nodes []Node,
normalizedEdges []Edge,
response hierarchyLayoutResponse,
) (hierarchyLayoutResult, error) {
positionByID := make(map[uint32]hierarchyLayoutResponseNode, len(response.Nodes))
for _, node := range response.Nodes {
if _, ok := positionByID[node.NodeID]; ok {
return hierarchyLayoutResult{}, fmt.Errorf("hierarchy layout bridge returned duplicate node_id %d", node.NodeID)
}
positionByID[node.NodeID] = node
}
filteredNodes := make([]Node, 0, len(response.Nodes))
keptNodeIDs := make(map[uint32]struct{}, len(response.Nodes))
for _, node := range nodes {
position, ok := positionByID[node.ID]
if !ok {
continue
}
node.X = position.X
node.Y = position.Y
filteredNodes = append(filteredNodes, node)
keptNodeIDs[node.ID] = struct{}{}
}
if len(filteredNodes) != len(response.Nodes) {
return hierarchyLayoutResult{}, fmt.Errorf("hierarchy layout bridge returned unknown node ids")
}
filteredEdges := make([]Edge, 0, len(normalizedEdges))
normalizedToFilteredEdge := make(map[int]int, len(normalizedEdges))
for normalizedIndex, edge := range normalizedEdges {
if _, ok := keptNodeIDs[edge.Source]; !ok {
continue
}
if _, ok := keptNodeIDs[edge.Target]; !ok {
continue
}
normalizedToFilteredEdge[normalizedIndex] = len(filteredEdges)
filteredEdges = append(filteredEdges, edge)
}
routeSegments := make([]RouteSegment, 0, len(response.RouteSegments))
for _, segment := range response.RouteSegments {
filteredEdgeIndex, ok := normalizedToFilteredEdge[segment.EdgeIndex]
if !ok {
return hierarchyLayoutResult{}, fmt.Errorf("hierarchy layout bridge returned route for unknown edge_index %d", segment.EdgeIndex)
}
points := make([]RoutePoint, 0, len(segment.Points))
for _, point := range segment.Points {
points = append(points, RoutePoint{
X: point.X,
Y: point.Y,
})
}
routeSegments = append(routeSegments, RouteSegment{
EdgeIndex: filteredEdgeIndex,
Kind: segment.Kind,
Points: points,
})
}
return hierarchyLayoutResult{
Nodes: filteredNodes,
Edges: filteredEdges,
RouteSegments: routeSegments,
}, nil
}
func runHierarchyLayoutBridge(
ctx context.Context,
cfg Config,
request hierarchyLayoutRequest,
) (hierarchyLayoutResponse, error) {
input, err := json.Marshal(request)
if err != nil {
return hierarchyLayoutResponse{}, fmt.Errorf("marshal hierarchy layout request failed: %w", err)
}
bridgeCtx, cancel := context.WithTimeout(ctx, cfg.HierarchyLayoutTimeout)
defer cancel()
cmd := exec.CommandContext(bridgeCtx, cfg.HierarchyLayoutBridgeBin)
cmd.Dir = cfg.HierarchyLayoutBridgeWorkdir
cmd.Stdin = bytes.NewReader(input)
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
if bridgeCtx.Err() != nil {
return hierarchyLayoutResponse{}, fmt.Errorf("hierarchy layout bridge timed out after %s", cfg.HierarchyLayoutTimeout)
}
detail := strings.TrimSpace(stderr.String())
if detail == "" {
detail = err.Error()
}
return hierarchyLayoutResponse{}, fmt.Errorf("hierarchy layout bridge failed: %s", detail)
}
var response hierarchyLayoutResponse
if err := json.Unmarshal(stdout.Bytes(), &response); err != nil {
detail := strings.TrimSpace(stderr.String())
if detail != "" {
return hierarchyLayoutResponse{}, fmt.Errorf("parse hierarchy layout bridge response failed: %v (stderr: %s)", err, detail)
}
return hierarchyLayoutResponse{}, fmt.Errorf("parse hierarchy layout bridge response failed: %w", err)
}
return response, nil
}
func layoutHierarchyWithRust(
ctx context.Context,
cfg Config,
nodes []Node,
edges []Edge,
preds *PredicateDict,
) (hierarchyLayoutResult, error) {
prepared := prepareHierarchyLayoutRequest(cfg.HierarchyLayoutRootIRI, nodes, edges, preds)
response, err := runHierarchyLayoutBridge(ctx, cfg, prepared.Request)
if err != nil {
return hierarchyLayoutResult{}, err
}
return applyHierarchyLayoutResponse(nodes, prepared.NormalizedEdges, response)
}