You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
4.8 KiB
Go
152 lines
4.8 KiB
Go
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
|
|
}
|
|
|
|
// Increment increments the logical time at the specified index of the vector clock.
|
|
//
|
|
// This method updates the logical time for a given process (specified by the index) by
|
|
// incrementing the corresponding value in the vector clock. It ensures that the index
|
|
// is within the bounds of the vector clock. If the index is out of bounds or the vector
|
|
// clock is uninitialized, an error is returned.
|
|
//
|
|
// Parameters:
|
|
//
|
|
// index (int): The index of the process whose logical time is to be incremented.
|
|
// It must be within the range of the vector clock's length.
|
|
//
|
|
// Returns:
|
|
//
|
|
// []T: A copy of the updated vector clock after the logical time at the given index
|
|
// has been incremented.
|
|
//
|
|
// error: An error is returned if the index is out of bounds or the vector clock is uninitialized.
|
|
//
|
|
// Note: Handling of potential overflow for the underlying type T (uint32 or uint64) is currently
|
|
// not implemented and should be handled accordingly if required.
|
|
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
|
|
}
|