Skip to content

Commit

Permalink
Feat: Added Utility functions to reactive package (#599)
Browse files Browse the repository at this point in the history
* Feat: Added Utility functions to reactive package

* Refactor: removed DeriveValueFrom to type

* Refactor: bound utility functions to type

* Fix: fixed nil pointer exception when using stringer

* Fix: fixed nil test
  • Loading branch information
hmoog authored Nov 7, 2023
1 parent 94ac829 commit afd931d
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 1 deletion.
14 changes: 14 additions & 0 deletions ds/reactive/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ type ReadableVariable[Type comparable] interface {
// Read executes the given function with the current value while read locking the variable.
Read(readFunc func(currentValue Type))

// WithValue is a utility function that allows to set up dynamic behavior based on the latest value of the
// ReadableVariable which is torn down once the value changes again (or the returned teardown function is called).
// It accepts an optional condition that has to be satisfied for the setup function to be called.
WithValue(setup func(value Type) (teardown func()), condition ...func(Type) bool) (teardown func())

// WithNonEmptyValue is a utility function that allows to set up dynamic behavior based on the latest (non-empty)
// value of the ReadableVariable which is torn down once the value changes again (or the returned teardown function
// is called).
WithNonEmptyValue(setup func(value Type) (teardown func())) (teardown func())

// OnUpdate registers the given callback that is triggered when the value changes.
OnUpdate(consumer func(oldValue, newValue Type), triggerWithInitialZeroValue ...bool) (unsubscribe func())

Expand Down Expand Up @@ -75,6 +85,10 @@ type WritableVariable[Type comparable] interface {

// InheritFrom inherits the value from the given ReadableVariable.
InheritFrom(other ReadableVariable[Type]) (unsubscribe func())

// DeriveValueFrom is a utility function that allows to derive a value from a newly created DerivedVariable.
// It returns a teardown function that unsubscribes the DerivedVariable from its inputs.
DeriveValueFrom(source DerivedVariable[Type]) (teardown func())
}

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
38 changes: 37 additions & 1 deletion ds/reactive/variable_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package reactive
import (
"log/slog"
"sync"
"unsafe"

"github.com/iotaledger/hive.go/ds"
"github.com/iotaledger/hive.go/lo"
Expand Down Expand Up @@ -67,6 +68,16 @@ func (v *variable[Type]) InheritFrom(other ReadableVariable[Type]) (unsubscribe
}, true)
}

// DeriveValueFrom is a utility function that allows to derive a value from a newly created DerivedVariable.
// It returns a teardown function that unsubscribes the DerivedVariable from its inputs.
func (v *variable[Type]) DeriveValueFrom(source DerivedVariable[Type]) (teardown func()) {
// no need to unsubscribe variable from source (it will no longer change and get garbage collected after
// unsubscribing from its inputs)
_ = v.InheritFrom(source)

return source.Unsubscribe
}

// updateValue atomically prepares the trigger by setting the new value and returning the new value, the previous value,
// the triggerID and the callbacks to trigger.
func (v *variable[Type]) updateValue(newValueGenerator func(Type) Type) (newValue, previousValue Type, triggerID uniqueID, callbacksToTrigger []*callback[func(prevValue, newValue Type)]) {
Expand Down Expand Up @@ -125,6 +136,24 @@ func (r *readableVariable[Type]) Read(readFunc func(currentValue Type)) {
readFunc(r.value)
}

// WithValue is a utility function that allows to set up dynamic behavior based on the latest value of the
// ReadableVariable which is torn down once the value changes again (or the returned teardown function is called).
// It accepts an optional condition that has to be satisfied for the setup function to be called.
func (r *readableVariable[Type]) WithValue(setup func(value Type) (teardown func()), condition ...func(Type) bool) (teardown func()) {
return r.OnUpdateWithContext(func(_, value Type, unsubscribeOnUpdate func(setup func() (teardown func()))) {
if len(condition) == 0 || condition[0](value) {
unsubscribeOnUpdate(func() func() { return setup(value) })
}
})
}

// WithNonEmptyValue is a utility function that allows to set up dynamic behavior based on the latest (non-empty)
// value of the ReadableVariable which is torn down once the value changes again (or the returned teardown function
// is called).
func (r *readableVariable[Type]) WithNonEmptyValue(setup func(value Type) (teardown func())) (teardown func()) {
return r.WithValue(setup, func(t Type) bool { return t != *new(Type) })
}

// OnUpdate registers the given callback that is triggered when the value changes.
func (r *readableVariable[Type]) OnUpdate(callback func(prevValue, newValue Type), triggerWithInitialZeroValue ...bool) (unsubscribe func()) {
r.valueMutex.Lock()
Expand Down Expand Up @@ -221,7 +250,9 @@ func (r *readableVariable[Type]) LogUpdates(logger VariableLogReceiver, logLevel

return logger.OnLogLevelActive(logLevel, func() (shutdown func()) {
return r.OnUpdate(func(_, newValue Type) {
if len(stringer) != 0 {
if isNil(newValue) {
logger.LogAttrs(logMessage, logLevel, slog.String("set", "nil"))
} else if len(stringer) != 0 {
logger.LogAttrs(logMessage, logLevel, slog.String("set", stringer[0](newValue)))
} else {
logger.LogAttrs(logMessage, logLevel, slog.Any("set", newValue))
Expand All @@ -230,6 +261,11 @@ func (r *readableVariable[Type]) LogUpdates(logger VariableLogReceiver, logLevel
})
}

// isNil returns true if the given value is nil.
func isNil(value any) bool {
return value == nil || (*[2]uintptr)(unsafe.Pointer(&value))[1] == 0
}

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////

// region derivedVariable //////////////////////////////////////////////////////////////////////////////////////////////
Expand Down

0 comments on commit afd931d

Please sign in to comment.