commit 5b42d917818ab94161578f25a931ccdd21d6b9b7 Author: Leon Mika Date: Sun Aug 23 08:15:12 2020 +1000 Initial commit diff --git a/dispatcher.go b/dispatcher.go new file mode 100644 index 0000000..3d01f94 --- /dev/null +++ b/dispatcher.go @@ -0,0 +1,68 @@ +package events + +type Dispatcher struct { + topics map[string]*topic +} + +func New() *Dispatcher { + return &Dispatcher{topics: make(map[string]*topic)} +} + +func (d *Dispatcher) On(event string, receiver interface{}) error { + // TODO: make thread safe + t, hasTopic := d.topics[event] + if !hasTopic { + t = &topic{} + d.topics[event] = t + } + + sub, err := newSubscriptionFromFunc(receiver) + if err != nil { + return err + } + + t.addSubscriber(sub) + return nil +} + +func (d *Dispatcher) Fire(event string, args ...interface{}) { + // TODO: make thead safe + topic, hasTopic := d.topics[event] + if !hasTopic { + return + } + + preparedArgs := prepareArgs(args) + + for sub := topic.head; sub != nil; sub = sub.next { + sub.handler.invoke(preparedArgs) + } +} + +type topic struct { + head *subscription + tail *subscription +} + +func (t *topic) addSubscriber(sub *subscription) { + if t.head == nil { + t.head = sub + } + if t.tail != nil { + t.tail.next = sub + } + t.tail = sub +} + +type subscription struct { + handler receiptHandler + next *subscription +} + +func newSubscriptionFromFunc(fn interface{}) (*subscription, error) { + handler, err := newReceiptHandler(fn) + if err != nil { + return nil, err + } + return &subscription{handler: handler, next: nil}, nil +} \ No newline at end of file diff --git a/dispatcher_test.go b/dispatcher_test.go new file mode 100644 index 0000000..c82614f --- /dev/null +++ b/dispatcher_test.go @@ -0,0 +1,38 @@ +package events + +import ( + "reflect" + "testing" +) + +func TestNew_Lifecycle(t *testing.T) { + receives := make([][]int, 0) + + d := New() + + d.On("event", func(x int, y int) { receives = append(receives, []int{1, x, y}) }) + d.On("event", func(x int) { receives = append(receives, []int{2, x}) }) + d.On("event", func(x int, y string, z string) { receives = append(receives, []int{3, x, len(y)}) }) + + d.Fire("event", 123, 123) + d.Fire("event", 234, 234) + d.Fire("event", "string", "value") + + assertEquals(t, [][]int{ + {1, 123, 123}, + {2, 123}, + {3, 123, 0}, + {1, 234, 234}, + {2, 234}, + {3, 234, 0}, + {1, 0, 0}, + {2, 0}, + {3, 0, 5}, + }, receives) +} + +func assertEquals(t testing.TB, expected interface{}, actual interface{}) { + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Expected %v but was %v", expected, actual) + } +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b8d56e2 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/lmika/events + +go 1.14 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..56d62e7 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..99dfd81 --- /dev/null +++ b/handler.go @@ -0,0 +1,46 @@ +// +build !tinygo + +package events + +import ( + "errors" + "reflect" +) + +type receiptHandler struct { + receiverFunc reflect.Value + funcType reflect.Type +} + +func newReceiptHandler(receiver interface{}) (receiptHandler, error) { + val := reflect.ValueOf(receiver) + if val.Type().Kind() != reflect.Func { + return receiptHandler{}, errors.New("not a function") + } + + return receiptHandler{receiverFunc: val, funcType: val.Type()}, nil +} + +func (rh *receiptHandler) invoke(values preparedArgs) { + args := make([]reflect.Value, rh.funcType.NumIn()) + for i := range args { + args[i] = reflect.Zero(rh.funcType.In(i)) + if i < len(values) { + if rh.funcType.In(i).AssignableTo(values[i].Type()) { + args[i] = values[i] + } + } + } + + rh.receiverFunc.Call(args) +} + +type preparedArgs []reflect.Value + +func prepareArgs(argIfacess []interface{}) preparedArgs { + values := make([]reflect.Value, len(argIfacess)) + for i, a := range argIfacess { + values[i] = reflect.ValueOf(a) + } + return values +} \ No newline at end of file diff --git a/handler_tinygo.go b/handler_tinygo.go new file mode 100644 index 0000000..1904d06 --- /dev/null +++ b/handler_tinygo.go @@ -0,0 +1,42 @@ +// +build tinygo + +package events + +import "errors" + +type receiverType int +const ( + receiverTypeUnknown receiverType = iota + receiverTypeFuncNoArgs +) + +type receiptHandler struct { + receiver interface{} + rt receiverType +} + +func newReceiptHandler(receiver interface{}) (receiptHandler, error) { + var rt receiverType = 0 + + switch receiver.(type) { + case func(): + rt = receiverTypeFuncNoArgs + default: + return receiptHandler{}, errors.New("unsupported receiver type") + } + + return receiptHandler{receiver: receiver, rt: rt}, nil +} + +func (rh *receiptHandler) invoke(values preparedArgs) { + switch rh.rt { + case receiverTypeFuncNoArgs: + rh.receiver.(func())() + } +} + +type preparedArgs []interface{} + +func prepareArgs(argIfacess []interface{}) preparedArgs { + return argIfacess +} \ No newline at end of file