Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Added Utility functions to reactive package #599

Merged
merged 5 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading