Skip to content

Commit

Permalink
feat(cycle): define notifications for life cycle events (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
plastikfan committed May 6, 2024
1 parent 294e742 commit 264f484
Show file tree
Hide file tree
Showing 13 changed files with 386 additions and 16 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"--fast"
],
"cSpell.words": [
"alloc",
"astrolib",
"bodyclose",
"cmds",
Expand Down
8 changes: 6 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ tasks:

# === test =================================================

dry:
cmds:
- ginkgo -v --dry-run ./...

t:
cmds:
- go test ./...
Expand All @@ -28,9 +32,9 @@ tasks:
cmds:
- go test ./i18n

dry:
tp:
cmds:
- ginkgo -v --dry-run ./...
- go test ./pref

# === ginkgo ================================================

Expand Down
3 changes: 3 additions & 0 deletions core/core-defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ package core

// core contains universal definitions and handles cross cutting concerns
// try to keep to a minimum to reduce rippling changes

type TraverseResult interface {
}
32 changes: 31 additions & 1 deletion cycle/cycle-defs.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,39 @@
package cycle

// cycle represents life cycle events
import (
"github.com/snivilised/traverse/core"
"github.com/snivilised/traverse/event"
)

// cycle represents life cycle events; can't use prefs

// beforeX
// afterX

// eg beforeOptions
// afterOptions

type (
Event[F any] interface {
// On subscribes to a life cycle event
On(handler F)
}

// SimpleHandler is a function that takes no parameters and can
// be used by any notification with this signature.
SimpleHandler func()

// BeginHandler invoked before traversal begins
BeginHandler func(root string)

// EndHandler invoked at the end of traversal
EndHandler func(result core.TraverseResult)

// HibernateHandler is a generic handler that is used by hibernation
// to indicate wake or sleep.
HibernateHandler func(description string)

// NodeHandler is a generic handler that is for any notification that contains
// the traversal node, such as directory ascend or descend.
NodeHandler func(node *event.Node)
)
13 changes: 13 additions & 0 deletions cycle/cycle_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cycle_test

import (
"testing"

. "github.com/onsi/ginkgo/v2" //nolint:revive // ok
. "github.com/onsi/gomega" //nolint:revive // ok
)

func TestCycle(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Cycle Suite")
}
156 changes: 156 additions & 0 deletions cycle/notify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package cycle

import (
"github.com/snivilised/traverse/core"
"github.com/snivilised/traverse/event"
)

type (
broadcasterFunc[F any] func(listeners []F) F

Dispatch[F any] struct {
Invoke F
broadcaster broadcasterFunc[F]
}

// NotificationCtrl contains the handler function to be invoked. The control
// is agnostic to the handler's signature and therefore can not invoke it.
NotificationCtrl[F any] struct {
Dispatch Dispatch[F]
subscribed bool
listeners []F
}

Events struct { // --> options
Ascend Event[NodeHandler]
Begin Event[BeginHandler]
Descend Event[NodeHandler]
End Event[EndHandler]
Start Event[HibernateHandler]
Stop Event[HibernateHandler]
}

Controls struct { // --> registry
Ascend NotificationCtrl[NodeHandler]
Begin NotificationCtrl[BeginHandler]
Descend NotificationCtrl[NodeHandler]
End NotificationCtrl[EndHandler]
Start NotificationCtrl[HibernateHandler]
Stop NotificationCtrl[HibernateHandler]
}
)

var (
AscendDispatcher Dispatch[NodeHandler]
BeginDispatcher Dispatch[BeginHandler]
DescendDispatcher Dispatch[NodeHandler]
EndDispatcher Dispatch[EndHandler]
StartDispatcher Dispatch[HibernateHandler]
StopDispatcher Dispatch[HibernateHandler]
)

func init() {
AscendDispatcher = Dispatch[NodeHandler]{
Invoke: func(_ *event.Node) {},
broadcaster: BroadcastNode,
}

BeginDispatcher = Dispatch[BeginHandler]{
Invoke: func(_ string) {},
broadcaster: BroadcastBegin,
}

DescendDispatcher = Dispatch[NodeHandler]{
Invoke: func(_ *event.Node) {},
broadcaster: BroadcastNode,
}

EndDispatcher = Dispatch[EndHandler]{
Invoke: func(_ core.TraverseResult) {},
broadcaster: BroadcastEnd,
}

StartDispatcher = Dispatch[HibernateHandler]{
Invoke: func(_ string) {},
broadcaster: BroadcastHibernate,
}

StopDispatcher = Dispatch[HibernateHandler]{
Invoke: func(_ string) {},
broadcaster: BroadcastHibernate,
}
}

// Bind attaches the underlying notification controllers to the
// Events.
func (e *Events) Bind(cs *Controls) {
e.Ascend = &cs.Ascend
e.Begin = &cs.Begin
e.Descend = &cs.Descend
e.End = &cs.End
e.Start = &cs.Start
e.Stop = &cs.Stop
}

// On subscribes to a life cycle event
func (c *NotificationCtrl[F]) On(handler F) {
if !c.subscribed {
c.Dispatch.Invoke = handler
c.subscribed = true

return
}

if c.listeners == nil {
const alloc = 2
c.listeners = make([]F, 0, alloc)
c.listeners = append(c.listeners, c.Dispatch.Invoke)
}

c.listeners = append(c.listeners, handler)
c.Dispatch.Invoke = c.broadcaster(c.listeners)
}

func (c *NotificationCtrl[F]) broadcaster(listeners []F) F {
return c.Dispatch.broadcaster(listeners)
}

func BroadcastBegin(listeners []BeginHandler) BeginHandler {
return func(root string) {
for _, listener := range listeners {
listener(root)
}
}
}

func BroadcastEnd(listeners []EndHandler) EndHandler {
return func(result core.TraverseResult) {
for _, listener := range listeners {
listener(result)
}
}
}

func BroadcastNode(listeners []NodeHandler) NodeHandler {
return func(node *event.Node) {
for _, listener := range listeners {
listener(node)
}
}
}

func BroadcastHibernate(listeners []HibernateHandler) HibernateHandler {
return func(description string) {
for _, listener := range listeners {
listener(description)
}
}
}

func BroadcastSimple(listeners []SimpleHandler) SimpleHandler {
return func() {
for _, listener := range listeners {
listener()
}
}
}
47 changes: 47 additions & 0 deletions cycle/notify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cycle_test

import (
. "github.com/onsi/ginkgo/v2" //nolint:revive // ok
. "github.com/onsi/gomega" //nolint:revive // ok

"github.com/snivilised/traverse/core"
"github.com/snivilised/traverse/cycle"
)

var _ = Describe("Notify", func() {
Context("foo", func() {
It("should:", func() {
const path = "/traversal-root"

var (
notifications cycle.Controls
taps cycle.Events
begun bool
ended bool
)

// init(registry->options):
//
taps.Bind(&notifications)

// client:
//
taps.Begin.On(func(root string) {
begun = true
Expect(root).To(Equal(path))
})

taps.End.On(func(_ core.TraverseResult) {
ended = true
})

// component side:
//
notifications.Begin.Dispatch.Invoke(path)
notifications.End.Dispatch.Invoke(nil)

Expect(begun).To(BeTrue())
Expect(ended).To(BeTrue())
})
})
})
2 changes: 1 addition & 1 deletion enums/hibernation-en.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const (
//
HibernationPending // pending-hibernation

// HibernationActive conditional listening is active (callback is invoked)
// HibernationAwake conditional listening is active (callback is invoked)
//
HibernationAwake // awake-hibernation

Expand Down
26 changes: 17 additions & 9 deletions pref/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package pref
import (
"log/slog"

"github.com/snivilised/traverse/cycle"
"github.com/snivilised/traverse/enums"
)

// package: pref contains user option definitions; do not use anything in nav (cyclic)
// package: pref contains user option definitions; do not use anything in kernel (cyclic)

type (
Options struct {
Expand All @@ -23,6 +24,10 @@ type (
//
Sampler SamplerOptions

// Events provides the ability to tap into life cycle events
//
Events cycle.Events

// Monitor contains externally provided logger
//
Monitor MonitorOptions
Expand All @@ -32,25 +37,26 @@ type (
OptionFn func(o *Options, reg *Registry) error
)

func requestOptions(with ...OptionFn) *Options {
o := getDetDefaultOptions()
reg := &Registry{}
func RequestOptions(reg *Registry, with ...OptionFn) *Options {
o := defaultOptions()
o.Events.Bind(&reg.Notification)

for _, functionalOption := range with {
for _, option := range with {
// TODO: check error
_ = functionalOption(o, reg)
_ = option(o, reg)
}

reg.O = o

return o
}

func getDetDefaultOptions() *Options {
func defaultOptions() *Options {
nopLogger := &slog.Logger{}

return &Options{
o := &Options{
Core: CoreOptions{
Subscription: enums.SubscribeUniversal,

Behaviours: NavigationBehaviours{
SubPath: SubPathBehaviour{
KeepTrailingSep: true,
Expand All @@ -72,4 +78,6 @@ func getDetDefaultOptions() *Options {
Log: nopLogger,
},
}

return o
}
Loading

0 comments on commit 264f484

Please sign in to comment.