fix: resolve simulator test compilation errors

- Fix SimulateCSIData to accept []*Walker instead of []*SimWalker
- Remove unused imports (path/filepath, time) from virtual_state_test.go
- Fix assignment mismatch for CreateVirtualNode error handling
- Fix deadlock in VirtualNodeStore by using saveLocked() when mutex is held
- Refactor CreateAPNode to avoid race condition with state modification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-04-10 21:16:39 -04:00
parent eaad8f789a
commit 1d80c9ba36
3 changed files with 78 additions and 25 deletions

View file

@ -486,3 +486,30 @@ func IsInFirstFresnelZone(tx, rx, point Point) bool {
return FresnelZoneNumber(tx, rx, point) == 1
}
// SimulateCSIData simulates CSI data for a set of links and walkers.
// Returns a map of link IDs to their maximum deltaRMS value across all walkers,
// but only includes links where deltaRMS is >= threshold.
func (pm *PropagationModel) SimulateCSIData(links []Link, walkers []*Walker, threshold float64) map[string]float64 {
results := make(map[string]float64)
for _, link := range links {
maxDeltaRMS := 0.0
// Compute deltaRMS for each walker position
for _, walker := range walkers {
deltaRMS := pm.ComputeLinkActivity(link, walker.Position, threshold)
if deltaRMS > maxDeltaRMS {
maxDeltaRMS = deltaRMS
}
}
// Only include links that meet the threshold
if maxDeltaRMS >= threshold {
linkID := link.TX.ID + "->" + link.RX.ID
results[linkID] = maxDeltaRMS
}
}
return results
}

View file

@ -117,8 +117,8 @@ func (s *VirtualNodeStore) CreateNode(id, name string, nodeType NodeType, positi
s.nodes[id] = state
// Persist to disk
if err := s.save(); err != nil {
// Persist to disk (mutex already held)
if err := s.saveLocked(); err != nil {
delete(s.nodes, id)
return nil, fmt.Errorf("save node: %w", err)
}
@ -133,19 +133,47 @@ func (s *VirtualNodeStore) CreateVirtualNode(id, name string, position Point) (*
// CreateAPNode creates a new access point node (for passive radar)
func (s *VirtualNodeStore) CreateAPNode(id, name, bssid string, channel int, position Point) (*VirtualNodeState, error) {
state, err := s.CreateNode(id, name, NodeTypeAP, position)
if err != nil {
return nil, err
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return nil, fmt.Errorf("store is closed")
}
s.mu.Lock()
state.Role = RoleTX
state.APBSSID = bssid
state.APChannel = channel
state.UpdatedAt = time.Now()
s.mu.Unlock()
if _, exists := s.nodes[id]; exists {
return nil, fmt.Errorf("node %s already exists", id)
}
if err := s.save(); err != nil {
// Validate position is within space bounds
minX, minY, minZ, maxX, maxY, maxZ := s.space.Bounds()
if position.X < minX || position.X > maxX ||
position.Y < minY || position.Y > maxY ||
position.Z < minZ || position.Z > maxZ {
return nil, fmt.Errorf("position (%f, %f, %f) is outside space bounds [%f, %f, %f] to [%f, %f, %f]",
position.X, position.Y, position.Z, minX, minY, minZ, maxX, maxY, maxZ)
}
now := time.Now()
state := &VirtualNodeState{
ID: id,
Name: name,
Type: NodeTypeAP,
Role: RoleTX,
Position: position,
Enabled: true,
CreatedAt: now,
UpdatedAt: now,
Metadata: make(map[string]interface{}),
Tags: make([]string, 0),
APBSSID: bssid,
APChannel: channel,
}
s.nodes[id] = state
// Persist to disk (mutex already held)
if err := s.saveLocked(); err != nil {
delete(s.nodes, id)
return nil, fmt.Errorf("save AP node: %w", err)
}
@ -196,7 +224,7 @@ func (s *VirtualNodeStore) UpdateNodePosition(id string, position Point) error {
state.Position = position
state.UpdatedAt = time.Now()
return s.save()
return s.saveLocked()
}
// UpdateNodeRole updates a node's role
@ -216,7 +244,7 @@ func (s *VirtualNodeStore) UpdateNodeRole(id string, role NodeRole) error {
state.Role = role
state.UpdatedAt = time.Now()
return s.save()
return s.saveLocked()
}
// SetNodeEnabled enables or disables a node
@ -236,7 +264,7 @@ func (s *VirtualNodeStore) SetNodeEnabled(id string, enabled bool) error {
state.Enabled = enabled
state.UpdatedAt = time.Now()
return s.save()
return s.saveLocked()
}
// UpdateNodeMetadata updates a node's metadata
@ -256,7 +284,7 @@ func (s *VirtualNodeStore) UpdateNodeMetadata(id string, metadata map[string]int
state.Metadata = metadata
state.UpdatedAt = time.Now()
return s.save()
return s.saveLocked()
}
// AddTag adds a tag to a node
@ -283,7 +311,7 @@ func (s *VirtualNodeStore) AddTag(id, tag string) error {
state.Tags = append(state.Tags, tag)
state.UpdatedAt = time.Now()
return s.save()
return s.saveLocked()
}
// RemoveTag removes a tag from a node
@ -315,7 +343,7 @@ func (s *VirtualNodeStore) RemoveTag(id, tag string) error {
state.Tags = newTags
state.UpdatedAt = time.Now()
return s.save()
return s.saveLocked()
}
// DeleteNode removes a node from the store
@ -333,7 +361,7 @@ func (s *VirtualNodeStore) DeleteNode(id string) error {
delete(s.nodes, id)
return s.save()
return s.saveLocked()
}
// ListNodes returns all nodes
@ -448,7 +476,7 @@ func (s *VirtualNodeStore) UpdateSpace(space *Space) error {
}
}
return s.save()
return s.saveLocked()
}
// Clear removes all nodes from the store
@ -462,7 +490,7 @@ func (s *VirtualNodeStore) Clear() error {
s.nodes = make(map[string]*VirtualNodeState)
return s.save()
return s.saveLocked()
}
// ToNodeSet converts the stored nodes to a NodeSet for simulation
@ -540,7 +568,7 @@ func (s *VirtualNodeStore) ImportFromNodeSet(nodeSet *NodeSet) error {
s.nodes[node.ID] = state
}
return s.save()
return s.saveLocked()
}
// Close closes the store and releases resources

View file

@ -2,9 +2,7 @@ package simulator
import (
"os"
"path/filepath"
"testing"
"time"
)
// Test helper to create a temporary store
@ -804,7 +802,7 @@ func TestVirtualNodeStore_Close(t *testing.T) {
}
// Operations should fail after close
err = store.CreateVirtualNode("node-2", "Should Fail", NewPoint(2.0, 3.0, 1.5))
_, err = store.CreateVirtualNode("node-2", "Should Fail", NewPoint(2.0, 3.0, 1.5))
if err == nil {
t.Error("Expected error when creating node after close")
}