Initial commit

This commit is contained in:
Leon Mika 2020-08-23 08:15:12 +10:00
commit 5b42d91781
6 changed files with 207 additions and 0 deletions

68
dispatcher.go Normal file
View file

@ -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
}

38
dispatcher_test.go Normal file
View file

@ -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)
}
}

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module github.com/lmika/events
go 1.14

10
go.sum Normal file
View file

@ -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=

46
handler.go Normal file
View file

@ -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
}

42
handler_tinygo.go Normal file
View file

@ -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
}