Documentation
¶
Overview ¶
Package changetracker provides variable management with automatic change detection.
The package provides:
- A change tracker that manages variables and detects changes
- Variables that hold values and track parent-child relationships
- Object registry with weak references for consistent object identity
- Value JSON serialization with object references
- Change detection via value comparison
- Pluggable value resolution for navigating into objects
Basic Usage ¶
Create a tracker and register variables:
tracker := changetracker.NewTracker()
data := &MyData{Count: 42}
root := tracker.CreateVariable(data, 0, "", nil)
// Create a child variable for a field
countVar := tracker.CreateVariable(nil, root.ID, "Count", nil)
// Modify value externally
data.Count = 100
// Detect changes
changes := tracker.DetectChanges()
Path Navigation ¶
Variables use dot-separated paths to navigate into values:
// Navigate to nested fields cityVar := tracker.CreateVariable(nil, root.ID, "Address.City", nil) // Use method calls in paths (requires appropriate access mode) nameVar := tracker.CreateVariable(nil, root.ID, "GetName()?access=r", nil) // Use setter methods (requires access "w" or "action") setterVar := tracker.CreateVariable(nil, root.ID, "SetValue(_)?access=w", nil)
Access Modes ¶
Variables support four access modes that control read/write permissions:
| Mode | Get | Set | Change Detection | Initial Value | |--------|-----|-----|------------------|---------------| | rw | OK | OK | Yes | Computed | | r | OK | Err | Yes | Computed | | w | Err | OK | No | Computed | | action | Err | OK | No | Skipped |
Path restrictions apply based on access mode:
- Paths ending in () require access "r" or "action"
- Paths ending in (_) require access "w" or "action"
The "action" mode is designed for variables that trigger side effects, where computing the initial value would invoke the action prematurely.
Priority System ¶
Values and properties can have priority levels (Low, Medium, High). Changes are returned sorted by priority (high first):
// Set priority via path query
v := tracker.CreateVariable(nil, root.ID, "Count?priority=high", nil)
// Set property with priority suffix
v.SetProperty("label:high", "Important")
Object Registry ¶
The tracker maintains a weak map from Go objects (pointers/maps) to variable IDs. This enables consistent object identity in Value JSON serialization:
alice := &Person{Name: "Alice"}
tracker.CreateVariable(alice, 0, "", nil) // ID 1
// Serialize to Value JSON - registered objects become {"obj": id}
json := tracker.ToValueJSON(alice) // {"obj": 1}
Unregistered objects found during ToValueJSON serialization are auto-registered. This enables arrays of objects to be properly serialized as arrays of object references:
people := []*Person{{Name: "Alice"}, {Name: "Bob"}}
json := tracker.ToValueJSON(people) // [{"obj": 2}, {"obj": 3}]
Per protocol spec: "Arrays contain only variable values (no nested objects, only references)"
Change Detection ¶
DetectChanges performs a depth-first traversal from root variables, comparing current values to cached Value JSON:
changes := tracker.DetectChanges()
for _, change := range changes {
fmt.Printf("Variable %d changed: value=%v props=%v\n",
change.VariableID, change.ValueChanged, change.PropertiesChanged)
}
Active/inactive variables control which subtrees participate in detection. Non-readable variables (access "w" or "action") are skipped.
Package changetracker provides variable management with automatic change detection. CRC: crc-Tracker.md, crc-Variable.md, crc-Resolver.md, crc-ObjectRef.md, crc-ObjectRegistry.md, crc-Change.md, crc-Priority.md Spec: main.md, api.md
Index ¶
- func GetObjectRefID(value any) (int64, bool)
- func IsObjectRef(value any) bool
- func JsonEqual(a, b any) bool
- type Change
- type ObjectRef
- type Priority
- type Resolver
- type Tracker
- func (t *Tracker) Call(obj any, methodName string) (any, error)
- func (t *Tracker) CallWith(obj any, methodName string, value any) error
- func (t *Tracker) ChangeAll(varID int64)
- func (t *Tracker) Children(parentID int64) []*Variable
- func (t *Tracker) ConvertToValueJSON(tracker *Tracker, value any) any
- func (t *Tracker) CreateValue(variable *Variable, typ string, value any) any
- func (t *Tracker) CreateVariable(value any, parentID int64, path string, properties map[string]string) *Variable
- func (t *Tracker) CreateVariableWithId(id int64, value any, parentID int64, path string, properties map[string]string) *Variable
- func (t *Tracker) CreateWrapper(variable *Variable) any
- func (t *Tracker) DestroyVariable(id int64)
- func (t *Tracker) DetectChanges() bool
- func (t *Tracker) Diag(level int, format string, args ...any)
- func (t *Tracker) FromValueJSONBytes(value []byte) (any, error)
- func (t *Tracker) Get(obj any, pathElement any) (any, error)
- func (t *Tracker) GetByIndex(rv reflect.Value, index int) (any, error)
- func (t *Tracker) GetByString(rv reflect.Value, name string) (any, error)
- func (t *Tracker) GetChanges() []Change
- func (t *Tracker) GetObject(objID int64) any
- func (t *Tracker) GetType(variable *Variable, value any) string
- func (t *Tracker) GetVariable(id int64) *Variable
- func (t *Tracker) LookupObject(obj any) (int64, bool)
- func (t *Tracker) RecordPropertyChange(varID int64, propName string)
- func (t *Tracker) RegisterObject(obj any) (int64, bool)
- func (t *Tracker) RootVariables() []*Variable
- func (t *Tracker) Set(obj any, pathElement any, value any) error
- func (t *Tracker) ToValueJSON(value any) any
- func (t *Tracker) ToValueJSONBytes(value any) ([]byte, error)
- func (t *Tracker) UnregisterObject(obj any)
- func (t *Tracker) Variables() []*Variable
- type Variable
- func (v *Variable) Get() (any, error)
- func (v *Variable) GetAccess() string
- func (v *Variable) GetId() int64
- func (v *Variable) GetProperty(name string) string
- func (v *Variable) GetPropertyPriority(name string) Priority
- func (v *Variable) GetValue() (any, error)
- func (v *Variable) IsAction() bool
- func (v *Variable) IsReadable() bool
- func (v *Variable) IsWritable() bool
- func (v *Variable) JsonForUpdate() any
- func (v *Variable) NavigationValue() any
- func (v *Variable) Parent() *Variable
- func (v *Variable) Set(value any) error
- func (v *Variable) SetActive(active bool)
- func (v *Variable) SetProperty(name, value string)
- func (v *Variable) SetType()
- type VariableError
- type VariableErrorType
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GetObjectRefID ¶
GetObjectRefID extracts the ID from an ObjectRef. CRC: crc-ObjectRef.md
func IsObjectRef ¶
IsObjectRef checks if a value is an ObjectRef. CRC: crc-ObjectRef.md
Types ¶
type Change ¶
type Change struct {
VariableID int64
Priority Priority
ValueChanged bool
PropertiesChanged []string
}
Change represents a change to a variable. CRC: crc-Change.md Spec: api.md
type ObjectRef ¶
type ObjectRef struct {
Obj int64 `json:"obj"`
}
ObjectRef represents an object reference in Value JSON form. CRC: crc-ObjectRef.md Spec: value-json.md
type Priority ¶
type Priority int
Priority represents priority level for values and properties. CRC: crc-Priority.md Spec: api.md
func ParsePriority ¶
ParsePriority converts a string to a Priority.
type Resolver ¶
type Resolver interface {
// Get retrieves a value at the given path element within obj.
// pathElement can be:
// - string: field name or map key
// - int: slice/array index (0-based)
Get(obj any, pathElement any) (any, error)
// Set assigns a value at the given path element within obj.
Set(obj any, pathElement any, value any) error
// Call invokes a zero-argument method and returns its result.
// Used for getter-style methods in path navigation.
Call(obj any, methodName string) (any, error)
// CallWith invokes a one-argument method with the given value.
// Return values are ignored.
// Used for setter-style methods at path terminals and variadic methods with rw access.
CallWith(obj any, methodName string, value any) error
// CreateValue creates a value for the given variable.
// This happens when CreateVariable has a "create" property
CreateValue(variable *Variable, typ string, value any) any
// CreateWrapper creates a wrapper object for the given variable.
// The wrapper stands in for the variable's value when child variables navigate paths.
// Returns nil if no wrapper is needed.
CreateWrapper(variable *Variable) any
// Get the type for a value
GetType(variable *Variable, value any) string
// ConvertToValueJSON converts a value to its JSON-serializable form.
// Custom resolvers override this to handle domain-specific types (e.g., Lua tables).
ConvertToValueJSON(tracker *Tracker, value any) any
}
Resolver is the interface for navigating into values. CRC: crc-Resolver.md Spec: resolver.md
type Tracker ¶
type Tracker struct {
Resolver Resolver // defaults to the tracker itself
DiagLevel int // diagnostic level (0 = disabled)
ChangeCount int64 // incremented each time DetectChanges() finds changes
PropertyChanges map[int64]*propertyChange // variables with property changes
// Diagnostics
ComputingVar *Variable // variable currently having its value computed
// contains filtered or unexported fields
}
Tracker is the central change tracker. CRC: crc-Tracker.md Spec: main.md, api.md
func NewTracker ¶
func NewTracker() *Tracker
NewTracker creates a new change tracker. Sequence: seq-create-variable.md
func (*Tracker) Call ¶
Call implements the Resolver interface for zero-arg method invocation. Sequence: seq-get-value.md
func (*Tracker) CallWith ¶
CallWith implements the Resolver interface for one-arg void method invocation. Sequence: seq-set-value.md
func (*Tracker) ConvertToValueJSON ¶
ConvertToValueJSON implements the Resolver interface. The default implementation returns the value unchanged.
func (*Tracker) CreateValue ¶
CreateWrapper implements the Resolver interface. The default implementation returns value (no creation).
func (*Tracker) CreateVariable ¶
func (t *Tracker) CreateVariable(value any, parentID int64, path string, properties map[string]string) *Variable
CreateVariable creates a new variable in the tracker with an auto-assigned ID. Sequence: seq-create-variable.md
func (*Tracker) CreateVariableWithId ¶
func (t *Tracker) CreateVariableWithId(id int64, value any, parentID int64, path string, properties map[string]string) *Variable
CreateVariableWithId creates a new variable in the tracker with a caller-specified ID. Returns nil if the ID is already in use. Sequence: seq-create-variable.md
func (*Tracker) CreateWrapper ¶
CreateWrapper implements the Resolver interface. The default implementation returns nil (no wrapper).
func (*Tracker) DestroyVariable ¶
DestroyVariable removes a variable from the tracker. CRC: crc-Tracker.md Sequence: seq-destroy-variable.md
func (*Tracker) DetectChanges ¶
DetectChanges collects active variables via tree traversal, then checks them in priority order (high → medium → low). CRC: crc-Tracker.md Sequence: seq-detect-changes.md
func (*Tracker) Diag ¶
Diag adds a diagnostic message to the currently-computing variable's Diags slice. CRC: crc-Tracker.md
func (*Tracker) FromValueJSONBytes ¶
func (*Tracker) Get ¶
Get implements the Resolver interface using reflection. Sequence: seq-get-value.md
func (*Tracker) GetByString ¶
func (*Tracker) GetChanges ¶
func (*Tracker) GetObject ¶
GetObject retrieves an object by its object ID. CRC: crc-Tracker.md, crc-ObjectRegistry.md
func (*Tracker) GetType ¶
GetType implements the Resolver interface. The default implementation returns "" (no type).
func (*Tracker) GetVariable ¶
GetVariable retrieves a variable by ID. CRC: crc-Tracker.md
func (*Tracker) LookupObject ¶
LookupObject finds the object ID for a registered object. CRC: crc-Tracker.md, crc-ObjectRegistry.md Sequence: seq-to-value-json.md
func (*Tracker) RecordPropertyChange ¶
RecordPropertyChange records that a property changed for a variable. CRC: crc-Tracker.md Sequence: seq-set-property.md
func (*Tracker) RegisterObject ¶
RegisterObject registers an object and returns its ID. Returns (id, true) if registered or already registered, (0, false) if not registerable. CRC: crc-Tracker.md, crc-ObjectRegistry.md Sequence: seq-to-value-json.md
func (*Tracker) RootVariables ¶
RootVariables returns variables with no parent (parentID == 0). CRC: crc-Tracker.md
func (*Tracker) Set ¶
Set implements the Resolver interface using reflection. Sequence: seq-set-value.md
func (*Tracker) ToValueJSON ¶
ToValueJSON serializes a value to Value JSON form. Sequence: seq-to-value-json.md Spec: protocol.md - "Arrays contain only variable values (no nested objects, only references)"
func (*Tracker) ToValueJSONBytes ¶
ToValueJSONBytes serializes a value to Value JSON as a byte slice. CRC: crc-Tracker.md
func (*Tracker) UnregisterObject ¶
UnregisterObject removes an object from the registry. CRC: crc-Tracker.md, crc-ObjectRegistry.md
type Variable ¶
type Variable struct {
ID int64
ParentID int64
ChildIDs []int64 // IDs of child variables (maintained automatically)
Active bool // whether this variable and its children are checked for changes
Access string // access mode: "r" (read-only), "w" (write-only), "rw" (read-write, default)
Properties map[string]string
PropertyPriorities map[string]Priority
Path []any // parsed path elements
Value any // cached value for child navigation
ValueJSON any // cached Value JSON for change detection
ValuePriority Priority // priority of the value
WrapperValue any // wrapper object for child navigation (optional)
WrapperJSON any // serialized WrapperValue
Error error // error from last get or nil if none
ComputeTime time.Duration // duration of the most recent value recomputation
MaxComputeTime time.Duration // maximum ComputeTime observed across all recomputations
ChangeCount int64 // number of times value changed during DetectChanges
Diags []string // diagnostics from most recent value recomputation
// contains filtered or unexported fields
}
Variable is a tracked variable. CRC: crc-Variable.md Spec: main.md, api.md, resolver.md
func (*Variable) Get ¶
Get gets the variable's value by navigating from the parent's value using the path. Sequence: seq-get-value.md
func (*Variable) GetAccess ¶
GetAccess returns the access mode of the variable. CRC: crc-Variable.md
func (*Variable) GetProperty ¶
GetProperty returns a property value, or empty string if not set. CRC: crc-Variable.md
func (*Variable) GetPropertyPriority ¶
GetPropertyPriority returns the priority for a property. CRC: crc-Variable.md
func (*Variable) GetValue ¶
GetValue is the internal method that navigates to the value without access checks. Used for caching values during CreateVariable and DetectChanges.
func (*Variable) IsAction ¶
IsAction returns true if the variable is an action trigger (access "action"). CRC: crc-Variable.md
func (*Variable) IsReadable ¶
IsReadable returns true if the variable allows reading (access "r" or "rw"). CRC: crc-Variable.md
func (*Variable) IsWritable ¶
IsWritable returns true if the variable allows writing (access "w", "rw", or "action"). CRC: crc-Variable.md
func (*Variable) JsonForUpdate ¶ added in v1.2.0
func (*Variable) NavigationValue ¶
NavigationValue returns the value used for child path navigation. Returns WrapperValue if present, otherwise Value. CRC: crc-Variable.md
func (*Variable) Parent ¶
Parent returns the parent variable, or nil if this is a root variable. CRC: crc-Variable.md
func (*Variable) Set ¶
Set sets the variable's value by navigating from the parent's value using the path. Sequence: seq-set-value.md
func (*Variable) SetActive ¶
SetActive sets whether the variable and its children should be checked for changes. CRC: crc-Variable.md
func (*Variable) SetProperty ¶
SetProperty sets a property. Empty value removes the property. Sequence: seq-set-property.md
type VariableError ¶
type VariableError struct {
ErrorType VariableErrorType
Message string
Cause error
}
func (*VariableError) Error ¶
func (v *VariableError) Error() string
type VariableErrorType ¶
type VariableErrorType int64
const ( NoError VariableErrorType = iota PathError NotFound BadSetterCall BadAccess BadIndex BadReference BadParent BadCall NilPath DeferredCode )
func (VariableErrorType) String ¶
func (e VariableErrorType) String() string