package clock import ( "errors" "fmt" ) type Uint interface { uint32 | uint64 } type VectorClock[T Uint] struct { clock []T } func max[T Uint](x, y T) T { if x >= y { return x } else { return y } } // GetClock returns a copy of the internal vector clock. // // This method provides a snapshot of the current state of the vector clock // without exposing the internal slice directly. By returning a copy, it ensures // that the original vector clock remains immutable and prevents unintended // modifications to the internal state. // // Returns: // // []T: A copy of the internal vector clock slice, where each element // represents the logical time for a corresponding process. The type T // is constrained by the Uint interface and can be either uint32 or uint64. func (vc *VectorClock[T]) GetClock() []T { clock := make([]T, len(vc.clock)) copy(clock, vc.clock) return clock } // Sync synchronizes the current VectorClock with another VectorClock of the same type. // // This method takes another VectorClock instance, compares the logical times for each // process, and updates the current VectorClock to hold the maximum logical time for // each process. The synchronization ensures that the resulting vector clock reflects // the latest logical times for both clocks. // // If the lengths of the two VectorClocks differ, an error is returned. // // Parameters: // // v VectorClock[T]: The other VectorClock instance to synchronize with. It must be // of the same type T (either uint32 or uint64), as constrained by the Uint interface. // // Returns: // // []T: A copy of the synchronized vector clock, where each element represents the // updated logical time for the corresponding process. // // error: An error is returned if the two VectorClocks have different lengths. func (vc *VectorClock[T]) Sync(v VectorClock[T]) ([]T, error) { compClock := v.GetClock() if len(vc.clock) != len(compClock) { return nil, errors.New("VectorClocks are of different lengths.") } for i := range vc.clock { vc.clock[i] = max(vc.clock[i], compClock[i]) } return vc.GetClock(), nil } func (vc *VectorClock[T]) Increment(index int) ([]T, error) { if index > len(vc.clock) || vc.clock == nil { return nil, errors.New(fmt.Sprintf("Cannot access index: %d, clock is of length %d", index, len(vc.clock))) } // TODO handle Uint overflow? vc.clock[index] = vc.clock[index] + 1 return vc.GetClock(), nil } // NewVectorClock creates a new generic VectorClock initialized to zero. // // This function creates a new instance of VectorClock for the specified type T, // which can be either uint32 or uint64, depending on the needs of the application. // // Parameters: // // size (T): The number of entries in the vector clock, typically corresponding // to the number of processes or nodes. The size is of type T, which can be uint32 // or uint64, as defined by the Uint constraint. // // Returns: // // *VectorClock[T]: A pointer to the newly created VectorClock instance, where all // elements of the clock are initialized to zero. func NewVectorClock[T Uint](size int) *VectorClock[T] { return &VectorClock[T]{ clock: make([]T, size), } } // ArrayToVectorClock converts an array of type T into a VectorClock instance. // // This function creates a new VectorClock where the internal clock is initialized // by copying the values from the provided array. The input array's values are // treated as the logical times for each process in the vector clock. // // Parameters: // // a []T: An array of type T (either uint32 or uint64) representing logical // times for each process. The type T is constrained by the Uint interface, which // ensures it is either uint32 or uint64. // // Returns: // // *VectorClock[T]: A pointer to a newly created VectorClock instance where the // internal clock is a copy of the provided array. func ArrayToVectorClock[T Uint](a []T) *VectorClock[T] { vc := &VectorClock[T]{ clock: make([]T, len(a)), } copy(vc.clock, a) return vc }