From 9410fc56394190e35cb63334f9c83e2ca7044c83 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 12 Mar 2022 11:02:22 -0600 Subject: [PATCH 01/41] event: overhaul api - adds type safe bindings / triggers - decreases api surface considerably - loops replaced with concurrent executions --- event/bind.go | 142 ++++++++++++++---------- event/bindingSet.go | 42 ------- event/bus.go | 216 ++++++++++++++---------------------- event/bus_test.go | 52 --------- event/caller.go | 73 +++++++++++-- event/callerMap.go | 104 ------------------ event/cid.go | 21 ---- event/default.go | 124 --------------------- event/doc.go | 2 +- event/entity.go | 53 --------- event/entity_test.go | 12 -- event/event_test.go | 249 ------------------------------------------ event/events.go | 53 +++++++++ event/handler.go | 164 +--------------------------- event/handler_test.go | 108 ------------------ event/internal.go | 56 ++++++++++ event/resolve.go | 135 ----------------------- event/resolve_test.go | 23 ---- event/response.go | 18 +-- event/strings.go | 50 --------- event/trigger.go | 105 ++++-------------- event/unbind.go | 50 --------- 22 files changed, 375 insertions(+), 1477 deletions(-) delete mode 100644 event/bindingSet.go delete mode 100644 event/bus_test.go delete mode 100644 event/callerMap.go delete mode 100644 event/cid.go delete mode 100644 event/default.go delete mode 100644 event/entity.go delete mode 100644 event/entity_test.go delete mode 100644 event/event_test.go create mode 100644 event/events.go delete mode 100644 event/handler_test.go create mode 100644 event/internal.go delete mode 100644 event/resolve.go delete mode 100644 event/resolve_test.go delete mode 100644 event/strings.go delete mode 100644 event/unbind.go diff --git a/event/bind.go b/event/bind.go index 4a0b654f..807ec4e3 100644 --- a/event/bind.go +++ b/event/bind.go @@ -1,71 +1,101 @@ package event -// Bind adds a function to the event bus tied to the given callerID -// to be called when the event name is triggered. It is equivalent to -// calling BindPriority with a zero Priority. -func (eb *Bus) Bind(name string, callerID CID, fn Bindable) { - eb.pendingMutex.Lock() - eb.binds = append(eb.binds, UnbindOption{ - Event: Event{ - Name: name, - CallerID: callerID, - }, Fn: fn}) - eb.pendingMutex.Unlock() -} +import "sync/atomic" -// PersistentBind acts like Bind, but persists the binding such that if the event -// bus is reset, the binding will still trigger. Thes bindings should likely be global -// bindings, using a CID of 0, or be tolerant to the CID bound not being present after -// such a clear. -func (eb *Bus) PersistentBind(name string, callerID CID, fn Bindable) { - eb.pendingMutex.Lock() - opt := UnbindOption{ - Event: Event{ - Name: name, - CallerID: callerID, - }, Fn: fn} - eb.binds = append(eb.binds, opt) - eb.persistentBinds = append(eb.persistentBinds, opt) - eb.pendingMutex.Unlock() -} +// Q: Why do Bind / Unbind / etc not immediately take effect? +// A: For concurrent safety, most operations on a bus lock the bus. Triggers acquire a read lock on the bus, +// as they iterate over internal bus components. Most logic within an event bus will happen from within +// a Trigger call-- when an entity is destroyed by some collision, for example, all of its bindings should +// be unregistered. If one were to call Unbind from within a + +// Q: Why not trust users to call Bind / Unbind / etc with `go`, to allow the caller to decide when to use +// concurrency? +// A: It is almost never correct to not call these functions with `go`, and it is a bad user experience for +// the engine to deadlock unexpectedly because you forgot to begin some call with a goroutine. -// ClearPersistentBindings removes all persistent bindings. It will not unbind them -// from the bus, but they will not be bound following the next bus reset. -func (eb *Bus) ClearPersistentBindings() { - eb.pendingMutex.Lock() - eb.persistentBinds = []UnbindOption{} - eb.pendingMutex.Unlock() +// A Binding, returned from calls to Bind, references the details of a binding and where that binding is +// stored within a handler. The common use case for this structure would involve a system that wanted to +// keep track of its bindings for later remote unbinding. This structure can also be used to construct +// and unbind a known reference. +type Binding struct { + Handler Handler + EventID UnsafeEventID + CallerID CallerID + BindID BindID } -// GlobalBind binds on the bus to the cid 0, a non entity. -func (eb *Bus) GlobalBind(name string, fn Bindable) { - eb.Bind(name, 0, fn) +// Unbind unbinds the callback associated with this binding from it's own event handler. If this binding +// does not belong to its handler or has already been unbound, this will do nothing. +func (b Binding) Unbind() { + b.Handler.Unbind(b) } -// Empty is a helper to convert a func() into a Bindable function signature. -func Empty(f func()) Bindable { - return func(CID, interface{}) int { - f() - return 0 +// A BindID is a unique identifier for a binding within a bus. +type BindID int64 + +// UnsafeBind registers a callback function to be called whenever the provided event is triggered +// against this bus. The binding is concurrently bound, and therefore may not be immediately +// available to be triggered. When Reset is called on a Bus, all prior bindings are unbound. This +// call is 'unsafe' because UnsafeBindables use bare interface{} types. +func (bus *Bus) UnsafeBind(eventID UnsafeEventID, callerID CallerID, fn UnsafeBindable) Binding { + bindID := BindID(atomic.AddInt64(bus.nextBindID, 1)) + go func() { + bus.mutex.Lock() + bus.getBindableList(eventID, callerID).storeBindable(fn, bindID) + bus.mutex.Unlock() + }() + return Binding{ + Handler: bus, + EventID: eventID, + CallerID: callerID, + BindID: bindID, } } -// WaitForEvent will return a single payload from the given event. This -// makes an internal binding, but that binding will clean itself up -// regardless of how this is used. This should be used in a select clause -// to ensure the signal is captured, if the signal comes and the output -// channel is not being waited on, the channel will be closed. -func (eb *Bus) WaitForEvent(name string) <-chan interface{} { - ch := make(chan interface{}) +// PersistentBind acts like UnsafeBind, but cause Bind to be called with these inputs after a Bus is Reset, i.e. +// persisting the binding through bus resets. Unbinding this will not stop it from being rebound on the next +// Bus Reset-- ClearPersistentBindings will. +func (bus *Bus) PersistentBind(eventID UnsafeEventID, callerID CallerID, fn UnsafeBindable) Binding { + binding := bus.UnsafeBind(eventID, callerID, fn) go func() { - eb.GlobalBind(name, func(c CID, i interface{}) int { - select { - case ch <- i: - default: - } - close(ch) - return UnbindSingle + bus.mutex.Lock() + bus.persistentBindings = append(bus.persistentBindings, persistentBinding{ + eventID: eventID, + callerID: callerID, + fn: fn, }) + bus.mutex.Unlock() }() - return ch + return binding +} + +// Unbind unregisters a binding from a bus concurrently. Once complete, triggers that would +// have previously caused the Bindable callback to execute will no longer do so. +func (bus *Bus) Unbind(loc Binding) { + go func() { + bus.mutex.Lock() + bus.getBindableList(loc.EventID, loc.CallerID).remove(loc.BindID) + bus.mutex.Unlock() + }() +} + +// A Bindable is a strongly typed callback function to be executed on Trigger. It must be paired +// with an event registered via RegisterEvent. +type Bindable[T any] func(CallerID, T) Response + +func Bind[T any](b Handler, ev EventID[T], c CallerID, fn Bindable[T]) Binding { + return b.UnsafeBind(ev.UnsafeEventID, c, func(c CallerID, f interface{}) Response { + tf := f.(T) + return fn(c, tf) + }) +} + +// UnsafeBindable defines the underlying signature of all bindings. +type UnsafeBindable func(CallerID, interface{}) Response + +func EmptyBinding(f func()) UnsafeBindable { + return func(ci CallerID, i interface{}) Response { + f() + return NoResponse + } } diff --git a/event/bindingSet.go b/event/bindingSet.go deleted file mode 100644 index 23dc6a5c..00000000 --- a/event/bindingSet.go +++ /dev/null @@ -1,42 +0,0 @@ -package event - -// A Mapping stores a slice of event names and bindings -type Mapping struct { - eventNames []string - binds []Bindable -} - -// A BindingSet stores sets of event mappings bound to string names. -// The use case for a BindingSet is for a character that can exist in multiple states, -// so that they can swiftly switch between the event bindings that define those -// states. -type BindingSet map[string]Mapping - -// Set makes a new EventMapping for BindingSet -func (b BindingSet) Set(setName string, mappingSets ...map[string]Bindable) BindingSet { - - numMappings := 0 - for _, m := range mappingSets { - numMappings += len(m) - - } - bindings := make([]Bindable, numMappings) - events := make([]string, numMappings) - i := 0 - for _, m := range mappingSets { - for k, v := range m { - bindings[i] = v - events[i] = k - i++ - } - } - - b[setName] = Mapping{eventNames: events, binds: bindings} - return b -} - -// RebindMapping resets the entity controlling this cid to only have the bindings -// in the passed in event mapping -func (cid CID) RebindMapping(mapping Mapping) { - cid.UnbindAllAndRebind(mapping.binds, mapping.eventNames) -} diff --git a/event/bus.go b/event/bus.go index c95f1655..ca8a59f9 100644 --- a/event/bus.go +++ b/event/bus.go @@ -1,51 +1,30 @@ package event import ( - "reflect" "sync" "time" -) - -// Bindable is a way of saying "Any function -// that takes a generic struct of data -// and returns an error can be bound". -type Bindable func(CID, interface{}) int -// BindableList just stores other relevant data -// that a list of bindables needs to -// operate efficiently -type bindableList struct { - sl []Bindable - // We keep track of where the next nil - // element in our list is, so we - // can let bindings know where they - // are by index, (we don't shift to - // fill empty spaces) and so we can - // fill that slot next when a - // new binding comes in. - nextEmpty int -} + "github.com/oakmound/oak/v3/oakerr" +) // A Bus stores bindables to be triggered by events type Bus struct { - bindingMap map[string]map[CID]*bindableList - doneCh chan struct{} - framesElapsed int - Ticker *time.Ticker - binds []UnbindOption - partUnbinds []Event - fullUnbinds []UnbindOption - unbinds []binding - unbindAllAndRebinds []UnbindAllOption - persistentBinds []UnbindOption - framerate int - refreshRate time.Duration - callerMap *CallerMap - - mutex sync.RWMutex - pendingMutex sync.Mutex + nextBindID *int64 + bindingMap map[UnsafeEventID]map[CallerID]bindableList + persistentBindings []persistentBinding + doneCh chan struct{} + framesElapsed int + ticker *time.Ticker + callerMap *CallerMap + + mutex sync.RWMutex +} - init sync.Once +// a persistentBinding is rebound every time the bus is reset. +type persistentBinding struct { + eventID UnsafeEventID + callerID CallerID + fn UnsafeBindable } // NewBus returns an empty event bus with an assigned caller map. If nil @@ -55,125 +34,88 @@ func NewBus(callerMap *CallerMap) *Bus { callerMap = DefaultCallerMap } return &Bus{ - bindingMap: make(map[string]map[CID]*bindableList), + nextBindID: new(int64), + bindingMap: make(map[UnsafeEventID]map[CallerID]bindableList), doneCh: make(chan struct{}), callerMap: callerMap, } } // SetCallerMap updates a bus to use a specific set of callers. -func (b *Bus) SetCallerMap(cm *CallerMap) { - b.callerMap = cm -} - -// An Event is an event name and an associated caller id -type Event struct { - Name string - CallerID CID -} - -// UnbindOption stores information necessary -// to unbind a bindable -type UnbindOption struct { - Event - Fn Bindable +func (bus *Bus) SetCallerMap(cm *CallerMap) { + bus.callerMap = cm } -// binding stores data necessary -// to trace back to a bindable function -// and remove it from a Bus. -type binding struct { - Event - index int -} - -// Reset empties out all transient portions of the bus. It will not stop -// an ongoing loop. -func (eb *Bus) Reset() { +// ClearPersistentBindings removes all persistent bindings. It will not unbind them +// from the bus, but they will not be bound following the next bus reset. +func (eb *Bus) ClearPersistentBindings() { eb.mutex.Lock() - eb.pendingMutex.Lock() - eb.bindingMap = make(map[string]map[CID]*bindableList) - eb.binds = []UnbindOption{} - eb.partUnbinds = []Event{} - eb.fullUnbinds = []UnbindOption{} - eb.unbinds = []binding{} - eb.unbindAllAndRebinds = []UnbindAllOption{} - for _, bindSet := range eb.persistentBinds { - list := eb.getBindableList(bindSet.Event) - list.storeBindable(bindSet.Fn) - } - eb.pendingMutex.Unlock() + eb.persistentBindings = eb.persistentBindings[:0] eb.mutex.Unlock() } -// UnbindAllOption stores information needed to unbind and rebind -type UnbindAllOption struct { - ub Event - bs []Event - bnds []Bindable -} - -// Store a bindable into a BindableList. -func (bl *bindableList) storeBindable(fn Bindable) int { - - i := bl.nextEmpty - if len(bl.sl) == i { - bl.sl = append(bl.sl, fn) - } else { - bl.sl[i] = fn - } - - // Find the next empty space - for len(bl.sl) != bl.nextEmpty && bl.sl[bl.nextEmpty] != nil { - bl.nextEmpty++ +// Reset unbinds all present, non-persistent bindings on the bus. +func (bus *Bus) Reset() { + bus.mutex.Lock() + bus.bindingMap = make(map[UnsafeEventID]map[CallerID]bindableList) + for _, pb := range bus.persistentBindings { + bus.UnsafeBind(pb.eventID, pb.callerID, pb.fn) } - - return i + bus.mutex.Unlock() } -// This scans linearly for the bindable -// This will cause an issue with closures! -// You can't unbind closures that don't have the -// same variable reference because this compares -// pointers! -// -// At all costs, this should be avoided, and -// returning UnbindSingle from the function -// itself is much safer! -func (bl *bindableList) removeBindable(fn Bindable) { - v := reflect.ValueOf(fn) - for i := 0; i < len(bl.sl); i++ { - v2 := reflect.ValueOf(bl.sl[i]) - if v2 == v { - bl.removeIndex(i) - return - } +// EnterLoop triggers Enter events at the specified rate +func (bus *Bus) EnterLoop(frameDelay time.Duration) { + // The logical loop. + // In order, it waits on receiving a signal to begin a logical frame. + // It then runs any functions bound to when a frame begins. + // It then allows a scene to perform it's loop operation. + bus.framesElapsed = 0 + if bus.ticker == nil { + bus.ticker = time.NewTicker(frameDelay) } + bus.doneCh = make(chan struct{}) + go func() { + bus.ticker.Reset(frameDelay) + frameDelayF64 := float64(frameDelay) + lastTick := time.Now() + for { + select { + case now := <-bus.ticker.C: + deltaTime := now.Sub(lastTick) + lastTick = now + <-bus.Trigger(Enter.UnsafeEventID, EnterPayload{ + FramesElapsed: bus.framesElapsed, + SinceLastFrame: deltaTime, + TickPercent: float64(deltaTime) / frameDelayF64, + }) + bus.framesElapsed++ + case <-bus.doneCh: + return + } + } + }() } -// Remove a bindable from a BindableList -func (bl *bindableList) removeBinding(b binding) { - bl.removeIndex(b.index) -} - -func (bl *bindableList) removeIndex(i int) { - if len(bl.sl) <= i { - return - } - - bl.sl[i] = nil - - if i < bl.nextEmpty { - bl.nextEmpty = i +// Stop ceases anything spawned by an ongoing UpdateLoop +func (bus *Bus) Stop() error { + if bus.ticker != nil { + bus.ticker.Stop() + bus.ticker = nil } + close(bus.doneCh) + return nil } -func (eb *Bus) getBindableList(opt Event) *bindableList { - if m := eb.bindingMap[opt.Name]; m == nil { - eb.bindingMap[opt.Name] = make(map[CID]*bindableList) - } - if m := eb.bindingMap[opt.Name][opt.CallerID]; m == nil { - eb.bindingMap[opt.Name][opt.CallerID] = new(bindableList) +// SetTick optionally updates the Logical System’s tick rate +// (while it is looping) to be frameRate. If this operation is not +// supported, it should return an error. +func (bus *Bus) SetEnterLoopRate(frameDelay time.Duration) error { + if bus.ticker == nil { + return oakerr.NotFound{ + InputName: "bus.ticker", + } } - return eb.bindingMap[opt.Name][opt.CallerID] + bus.ticker.Reset(frameDelay) + return nil } diff --git a/event/bus_test.go b/event/bus_test.go deleted file mode 100644 index bde6a6d9..00000000 --- a/event/bus_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package event - -import ( - "fmt" - "testing" - "time" -) - -func TestBusStop(t *testing.T) { - b := NewBus(nil) - b.Ticker = time.NewTicker(10000 * time.Second) - phase := 0 - wait := make(chan struct{}) - var topErr error - go func() { - if err := b.Stop(); err != nil { - topErr = fmt.Errorf("stop errored: %v", err) - } - if phase != 1 { - topErr = fmt.Errorf("expected phase %v, got %v", 1, phase) - } - wait <- struct{}{} - }() - phase = 1 - - <-b.doneCh - <-wait - if topErr != nil { - t.Fatal(topErr) - } -} - -func TestBusPersistentBind(t *testing.T) { - t.Parallel() - b := NewBus(nil) - ev := "eventName" - calls := 0 - b.PersistentBind(ev, 0, func(c CID, i interface{}) int { - calls++ - return 0 - }) - b.Flush() - <-b.TriggerBack(ev, nil) - if calls != 1 { - t.Fatalf("expected binding to be called once, was called %d time(s)", calls) - } - b.Reset() - <-b.TriggerBack(ev, nil) - if calls != 2 { - t.Fatalf("expected binding to be called twice, was called %d time(s)", calls) - } -} diff --git a/event/caller.go b/event/caller.go index b7515c40..4f26e694 100644 --- a/event/caller.go +++ b/event/caller.go @@ -1,11 +1,68 @@ package event -// A Caller can bind, unbind and trigger events. -type Caller interface { - Trigger(string, interface{}) - Bind(string, Bindable) - UnbindAll() - UnbindAllAndRebind([]Bindable, []string) - E() interface{} - Parse(Entity) CID +import ( + "sync" + "sync/atomic" +) + +// A CID is a caller ID that entities use to trigger and bind functionality +type CallerID int64 + +const Global CallerID = 0 + +// A CallerMap tracks CID mappings to Entities. +// This is an alternative to passing in the entity via closure scoping, +// and allows for more general bindings as simple top level functions. +type CallerMap struct { + highestID *int64 + callersLock sync.RWMutex + callers map[CallerID]interface{} +} + +// NewCallerMap creates a caller map. A CallerMap +// is not valid for use if not created via this function. +func NewCallerMap() *CallerMap { + return &CallerMap{ + highestID: new(int64), + callers: map[CallerID]interface{}{}, + } +} + +// DefaultCallerMap is the caller map used by all event package caller +// functions. +var DefaultCallerMap = NewCallerMap() + +// NextID finds the next available caller id +// and returns it, after adding the given entity to +// the caller map. +func (cm *CallerMap) Register(e interface{}) CallerID { + nextID := atomic.AddInt64(cm.highestID, 1) + cm.callersLock.Lock() + cm.callers[CallerID(nextID)] = e + cm.callersLock.Unlock() + return CallerID(nextID) +} + +// GetEntity returns the entity corresponding to the given ID within +// the caller map. If no entity is found, it returns nil. +func (cm *CallerMap) GetEntity(id CallerID) interface{} { + cm.callersLock.RLock() + defer cm.callersLock.RUnlock() + return cm.callers[id] +} + +// HasEntity returns whether the given caller id is an initialized entity +// within the caller map. +func (cm *CallerMap) HasEntity(id CallerID) bool { + cm.callersLock.RLock() + defer cm.callersLock.RUnlock() + _, ok := cm.callers[id] + return ok +} + +// DestroyEntity removes an entity from the caller map. +func (cm *CallerMap) DestroyEntity(id CallerID) { + cm.callersLock.Lock() + delete(cm.callers, id) + cm.callersLock.Unlock() } diff --git a/event/callerMap.go b/event/callerMap.go deleted file mode 100644 index 49512f7a..00000000 --- a/event/callerMap.go +++ /dev/null @@ -1,104 +0,0 @@ -package event - -import ( - "sync" - "sync/atomic" -) - -// A CallerMap tracks CID mappings to Entities. Its intended use is -// to be a source of truth within event bindings for what entity the -// binding is triggering on: -// var cm *event.CallerMap -// func(cid event.CID, payload interface{}) int { -// ent := cm.GetEntity(cid) -// f, ok := ent.(*Foo) -// if !ok { -// // bound to an unexpected entity type! -// return event.UnbindSingle -// } -// // ... -// } -// This is an alternative to passing in the entity via closure scoping, -// and allows for more general bindings as simple top level functions. -type CallerMap struct { - highestID *int64 - callersLock sync.RWMutex - callers map[CID]Entity -} - -// NewCallerMap creates a caller map. A CallerMap -// is not valid for use if not created via this function. -func NewCallerMap() *CallerMap { - return &CallerMap{ - highestID: new(int64), - callers: map[CID]Entity{}, - } -} - -// DefaultCallerMap is the caller map used by all event package caller -// functions. -var DefaultCallerMap = NewCallerMap() - -// NextID finds the next available caller id -// and returns it, after adding the given entity to -// the caller map. -func (cm *CallerMap) NextID(e Entity) CID { - nextID := atomic.AddInt64(cm.highestID, 1) - cm.callersLock.Lock() - cm.callers[CID(nextID)] = e - cm.callersLock.Unlock() - return CID(nextID) -} - -// GetEntity returns the entity corresponding to the given ID within -// the caller map. If no entity is found, it returns nil. -func (cm *CallerMap) GetEntity(id CID) Entity { - cm.callersLock.RLock() - defer cm.callersLock.RUnlock() - return cm.callers[id] -} - -// HasEntity returns whether the given caller id is an initialized entity -// within the caller map. -func (cm *CallerMap) HasEntity(id CID) bool { - cm.callersLock.RLock() - defer cm.callersLock.RUnlock() - _, ok := cm.callers[id] - return ok -} - -// DestroyEntity removes an entity from the caller map. -func (cm *CallerMap) DestroyEntity(id CID) { - cm.callersLock.Lock() - delete(cm.callers, id) - cm.callersLock.Unlock() -} - -// NextID finds the next available caller id -// and returns it, after adding the given entity to -// the default caller map. -func NextID(e Entity) CID { - return DefaultCallerMap.NextID(e) -} - -// GetEntity returns the entity corresponding to the given ID within -// the default caller map. If no entity is found, it returns nil. -func GetEntity(id CID) Entity { - return DefaultCallerMap.GetEntity(id) -} - -// HasEntity returns whether the given caller id is an initialized entity -// within the default caller map. -func HasEntity(id CID) bool { - return DefaultCallerMap.HasEntity(id) -} - -// DestroyEntity removes an entity from the default caller map. -func DestroyEntity(id CID) { - DefaultCallerMap.DestroyEntity(id) -} - -// ResetCallerMap resets the DefaultCallerMap to be empty. -func ResetCallerMap() { - *DefaultCallerMap = *NewCallerMap() -} diff --git a/event/cid.go b/event/cid.go deleted file mode 100644 index 14a2946b..00000000 --- a/event/cid.go +++ /dev/null @@ -1,21 +0,0 @@ -package event - -// A CID is a caller ID that entities use to trigger and bind functionality -type CID int - -// E is shorthand for GetEntity(int(cid)) -func (cid CID) E() interface{} { - return GetEntity(cid) -} - -// Parse returns the given cid, or the entity's cid -// if the given cid is 0. This way, multiple entities can be -// composed together by passing 0 down to lower tiered constructors, so that -// the topmost entity is stored once and bind functions will -// bind to the topmost entity. -func (cid CID) Parse(e Entity) CID { - if cid == 0 { - return e.Init() - } - return cid -} diff --git a/event/default.go b/event/default.go deleted file mode 100644 index c9c7a277..00000000 --- a/event/default.go +++ /dev/null @@ -1,124 +0,0 @@ -package event - -// As in collision and mouse, default.go lists functions that -// only operate on DefaultBus, a package global bus. - -var ( - // DefaultBus is a bus that has additional operations for CIDs, and can - // be called via event.Call as opposed to bus.Call - DefaultBus = NewBus(DefaultCallerMap) -) - -// Trigger an event, but only for one ID, on the default bus -func (cid CID) Trigger(eventName string, data interface{}) { - go func(eventName string, data interface{}) { - DefaultBus.mutex.RLock() - if idMap, ok := DefaultBus.bindingMap[eventName]; ok { - if bs, ok := idMap[cid]; ok { - DefaultBus.triggerDefault(bs.sl, cid, eventName, data) - } - } - DefaultBus.mutex.RUnlock() - }(eventName, data) -} - -// TriggerBus triggers an event with some payload for this cid against the provided bus. -func (cid CID) TriggerBus(eventName string, data interface{}, bus Handler) chan struct{} { - return bus.TriggerCIDBack(cid, eventName, data) -} - -// Bind on a CID is shorthand for bus.Bind(name, cid, fn), on the default bus. -func (cid CID) Bind(name string, fn Bindable) { - DefaultBus.Bind(name, cid, fn) -} - -// UnbindAll removes all events with the given cid from the event bus -func (cid CID) UnbindAll() { - DefaultBus.UnbindAll(Event{ - Name: "", - CallerID: cid, - }) -} - -// UnbindAllAndRebind on a CID is equivalent to bus.UnbindAllAndRebind(..., cid) -func (cid CID) UnbindAllAndRebind(binds []Bindable, events []string) { - DefaultBus.UnbindAllAndRebind(Event{ - Name: "", - CallerID: cid, - }, binds, cid, events) -} - -// Trigger calls Trigger on the DefaultBus -func Trigger(eventName string, data interface{}) { - DefaultBus.Trigger(eventName, data) -} - -// TriggerBack calls TriggerBack on the DefaultBus -func TriggerBack(eventName string, data interface{}) chan struct{} { - return DefaultBus.TriggerBack(eventName, data) -} - -// GlobalBind calls GlobalBind on the DefaultBus -func GlobalBind(name string, fn Bindable) { - DefaultBus.GlobalBind(name, fn) -} - -// UnbindAll calls UnbindAll on the DefaultBus -func UnbindAll(opt Event) { - DefaultBus.UnbindAll(opt) -} - -// UnbindAllAndRebind calls UnbindAllAndRebind on the DefaultBus -func UnbindAllAndRebind(bo Event, binds []Bindable, cid CID, events []string) { - DefaultBus.UnbindAllAndRebind(bo, binds, cid, events) -} - -// UnbindBindable calls UnbindBindable on the DefaultBus -func UnbindBindable(opt UnbindOption) { - DefaultBus.UnbindBindable(opt) -} - -// Bind calls Bind on the DefaultBus -func Bind(name string, callerID CID, fn Bindable) { - DefaultBus.Bind(name, callerID, fn) -} - -// Flush calls Flush on the DefaultBus -func Flush() error { - return DefaultBus.Flush() -} - -// FramesElapsed calls FramesElapsed on the DefaultBus -func FramesElapsed() int { - return DefaultBus.FramesElapsed() -} - -// Reset calls Reset on the DefaultBus -func Reset() { - DefaultBus.Reset() -} - -// ResolveChanges calls ResolveChanges on the DefaultBus -func ResolveChanges() { - DefaultBus.ResolveChanges() -} - -// SetTick calls SetTick on the DefaultBus -func SetTick(framerate int) error { - return DefaultBus.SetTick(framerate) -} - -// Stop calls Stop on the DefaultBus -func Stop() error { - return DefaultBus.Stop() -} - -// Update calls Update on the DefaultBus -func Update() error { - return DefaultBus.Update() -} - -// UpdateLoop calls UpdateLoop on the DefaultBus -func UpdateLoop(framerate int, updateCh chan struct{}) error { - return DefaultBus.UpdateLoop(framerate, updateCh) -} diff --git a/event/doc.go b/event/doc.go index af897110..33069b21 100644 --- a/event/doc.go +++ b/event/doc.go @@ -1,2 +1,2 @@ -// Package event provides structures to propagate event occurences to subscribed system entities. +// Package event provides structures to propagate event occurrences to subscribed system entities. package event diff --git a/event/entity.go b/event/entity.go deleted file mode 100644 index 50345953..00000000 --- a/event/entity.go +++ /dev/null @@ -1,53 +0,0 @@ -package event - -// An Entity is an element which can be bound to, -// in that it has a CID. All Entities need to implement -// is an Init function which should call NextID(e) and -// return that id: -// func (f *Foo) Init() event.CID { -// f.CID = event.NextID(f) -// return f.CID -// } -// In a multi-window setup each window may have its own -// callerMap, in which case event.NextID should be replaced -// with a NextID call on the appropriate callerMap. -type Entity interface { - Init() CID -} - -// Q: Why does every entity need its own implementation -// of Init()? Why can't it get that method definition -// from struct embedding? -// -// A: Because the CallerMap will store whatever struct is -// passed in to NextID. In a naive implementation: -// type A struct { -// DefaultEntity -// } -// -// type DefaultEntity struct { -// event.CID -// } -// -// func (de *DefaultEntity) Init() event.CID { -// de.CID = event.NextID(de) -// return de.CID -// } -// -// func main() { -// ... -// a := &A{} -// cid := a.Init() -// ent := event.GetEntity(cid) -// _, ok := ent.(*A) -// // ok is false, ent is type *DefaultEntity -// } -// -// So to effectively do this you would need something like: -// func DefaultEntity(parent interface{}) *DefaultEntity {} -// ... where the structure would store and pass down the parent. -// This introduces empty interfaces, would make initialization -// more difficult, and would use slightly more memory. -// -// Feel free to use this idea in your own implementations, but -// this package will not provide this structure at this time. diff --git a/event/entity_test.go b/event/entity_test.go deleted file mode 100644 index 212a2aff..00000000 --- a/event/entity_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package event - -import ( - "testing" -) - -func TestGetEntityFails(t *testing.T) { - entity := GetEntity(100) - if entity != nil { - t.Fatalf("expected nil entity, got %v", entity) - } -} diff --git a/event/event_test.go b/event/event_test.go deleted file mode 100644 index 1f689c98..00000000 --- a/event/event_test.go +++ /dev/null @@ -1,249 +0,0 @@ -package event - -import ( - "testing" - "time" -) - -func sleep() { - // this is effectively "sync", or wait for the previous - // goroutine job to get its job done (Trigger - // use channels for 'done' signals because we don't want - // to enable users to wait on triggers that won't actually - // happen -because they are waiting- within a call that is - // holding a lock) - time.Sleep(200 * time.Millisecond) -} - -func TestBus(t *testing.T) { - triggers := 0 - go ResolveChanges() - GlobalBind("T", Empty(func() { - triggers++ - })) - sleep() - <-TriggerBack("T", nil) - if triggers != 1 { - t.Fatalf("first trigger did not happen") - } - Trigger("T", nil) - sleep() - if triggers != 2 { - t.Fatalf("second trigger did not happen") - } -} - -func TestUnbind(t *testing.T) { - triggers := 0 - go ResolveChanges() - GlobalBind("T", func(CID, interface{}) int { - triggers++ - return UnbindSingle - }) - sleep() - <-TriggerBack("T", nil) - if triggers != 1 { - t.Fatalf("first trigger did not happen") - } - sleep() - Trigger("T", nil) - sleep() - if triggers != 1 { - t.Fatalf("second trigger after unbind happened") - } - GlobalBind("T", func(CID, interface{}) int { - triggers++ - return 0 - }) - GlobalBind("T", func(CID, interface{}) int { - triggers++ - return UnbindEvent - }) - sleep() - Trigger("T", nil) - sleep() - if triggers != 3 { - t.Fatalf("global triggers did not happen") - } - - Trigger("T", nil) - sleep() - if triggers != 3 { - t.Fatalf("global triggers happened after unbind") - } - - GlobalBind("T", func(CID, interface{}) int { - triggers++ - return 0 - }) - sleep() - Trigger("T", nil) - sleep() - if triggers != 4 { - t.Fatalf("global triggers did not happen") - } - - Reset() - - Trigger("T", nil) - sleep() - if triggers != 4 { - t.Fatalf("global triggers did not unbind after reset") - } -} - -type ent struct{} - -func (e ent) Init() CID { - return NextID(e) -} - -func TestCID(t *testing.T) { - triggers := 0 - go ResolveChanges() - cid := CID(0).Parse(ent{}) - cid.Bind("T", func(CID, interface{}) int { - triggers++ - return 0 - }) - sleep() - cid.Trigger("T", nil) - sleep() - if triggers != 1 { - t.Fatalf("first trigger did not happen") - } - - // UnbindAllAndRebind - cid.UnbindAllAndRebind([]Bindable{ - func(CID, interface{}) int { - triggers-- - return 0 - }, - }, []string{ - "T", - }) - - sleep() - cid.Trigger("T", nil) - sleep() - if triggers != 0 { - t.Fatalf("second trigger did not happen") - } - - // UnbindAll - cid.UnbindAll() - - sleep() - cid.Trigger("T", nil) - sleep() - if triggers != 0 { - t.Fatalf("second trigger did not unbind") - } - - cid.Bind("T", func(CID, interface{}) int { - panic("Should not have been triggered") - }) - - // ResetEntities, etc - ResetCallerMap() - - cid.Trigger("T", nil) - sleep() -} - -func TestEntity(t *testing.T) { - go ResolveChanges() - e := ent{} - cid := e.Init() - cid2 := cid.Parse(e) - if cid != cid2 { - t.Fatalf("expected id %v got %v", cid, cid2) - } - if _, ok := cid.E().(ent); !ok { - t.Fatalf("cid entity was not present") - } - DestroyEntity(cid) - if cid.E() != nil { - t.Fatalf("cid entity was not deleted") - } -} - -var ( - ubTriggers int -) - -func TestUnbindBindable(t *testing.T) { - go ResolveChanges() - GlobalBind("T", tBinding) - sleep() - Trigger("T", nil) - sleep() - if ubTriggers != 1 { - t.Fatalf("first trigger did not happen") - } - // Fix this syntax - UnbindBindable( - UnbindOption{ - Event: Event{ - Name: "T", - CallerID: 0, - }, - Fn: tBinding, - }, - ) - sleep() - Trigger("T", nil) - sleep() - if ubTriggers != 1 { - t.Fatalf("unbind call did not unbind trigger") - } -} - -func tBinding(CID, interface{}) int { - ubTriggers++ - return 0 -} - -func TestBindableList(t *testing.T) { - bl := new(bindableList) - bl.sl = make([]Bindable, 10) - bl.removeIndex(11) - bl.sl[2] = tBinding - bl.removeBindable(tBinding) - // Assert nothing panicked -} - -func TestUnbindAllAndRebind(t *testing.T) { - go ResolveChanges() - UnbindAllAndRebind( - Event{ - Name: "T", - CallerID: 0, - }, []Bindable{}, 0, []string{}) -} - -func TestBindingSet(t *testing.T) { - triggers := 0 - bs := BindingSet{} - bs.Set("one", map[string]Bindable{ - "T": func(CID, interface{}) int { - triggers++ - return 0 - }, - "P": func(CID, interface{}) int { - triggers *= 2 - return 0 - }, - }) - e := ent{} - cid := e.Init() - cid.RebindMapping(bs["one"]) - sleep() - cid.Trigger("T", nil) - sleep() - cid.Trigger("P", nil) - sleep() - if triggers != 2 { - t.Fatalf("triggers did not happen") - } -} diff --git a/event/events.go b/event/events.go new file mode 100644 index 00000000..e8a444bf --- /dev/null +++ b/event/events.go @@ -0,0 +1,53 @@ +package event + +import ( + "sync/atomic" + "time" + + "github.com/oakmound/oak/v3/alg/intgeom" +) + +type UnsafeEventID int64 + +type EventID[T any] struct { + UnsafeEventID +} + +var ( + nextEventID int64 +) + +const NoEvent = 0 + +func RegisterEvent[T any]() EventID[T] { + id := atomic.AddInt64(&nextEventID, 1) + return EventID[T]{ + UnsafeEventID: UnsafeEventID(id), + } +} + +type NoPayload struct{} + +// EnterPayload is the payload sent down to Enter bindings +type EnterPayload struct { + FramesElapsed int + SinceLastFrame time.Duration + TickPercent float64 +} + +var ( + // Enter: the beginning of every logical frame. + Enter = RegisterEvent[EnterPayload]() + // AnimationEnd: Triggered on animations CIDs when they loop from the last to the first frame + AnimationEnd = RegisterEvent[NoPayload]() + // ViewportUpdate: Triggered when the position of of the viewport changes + ViewportUpdate = RegisterEvent[intgeom.Point2]() + // OnStop: Triggered when the engine is stopped. + OnStop = RegisterEvent[NoPayload]() + // FocusGain: Triggered when the window gains focus + FocusGain = RegisterEvent[NoPayload]() + // FocusLoss: Triggered when the window loses focus + FocusLoss = RegisterEvent[NoPayload]() + // InputChange: triggered when the most recent input device changes (e.g. keyboard to joystick or vice versa) + InputChange = RegisterEvent[NoPayload]() +) diff --git a/event/handler.go b/event/handler.go index 6088763a..7a64fd23 100644 --- a/event/handler.go +++ b/event/handler.go @@ -1,10 +1,7 @@ package event import ( - "math" "time" - - "github.com/oakmound/oak/v3/timing" ) var ( @@ -15,162 +12,13 @@ var ( // for use in oak internally, and thus the functions that need to be replaced // by alternative event handlers. type Handler interface { - WaitForEvent(name string) <-chan interface{} - // - UpdateLoop(framerate int, updateCh chan struct{}) error - FramesElapsed() int - SetTick(framerate int) error - Update() error - Flush() error + EnterLoop(time.Duration) + SetEnterLoopRate(time.Duration) error Stop() error Reset() - SetRefreshRate(time.Duration) - // - Trigger(event string, data interface{}) - TriggerBack(event string, data interface{}) chan struct{} - TriggerCIDBack(cid CID, eventName string, data interface{}) chan struct{} - // - Pause() - Resume() - // - Bind(string, CID, Bindable) - GlobalBind(string, Bindable) - UnbindAll(Event) - UnbindAllAndRebind(Event, []Bindable, CID, []string) - UnbindBindable(UnbindOption) -} - -// A CallerMapper has an internal caller map that can be set. -type CallerMapper interface { + TriggerForCaller(cid CallerID, event UnsafeEventID, data interface{}) chan struct{} + Trigger(event UnsafeEventID, data interface{}) chan struct{} + UnsafeBind(UnsafeEventID, CallerID, UnsafeBindable) Binding + Unbind(Binding) SetCallerMap(*CallerMap) } - -// A PersistentBinder can persist bindings through bus resets -type PersistentBinder interface { - PersistentBind(string, CID, Bindable) - ClearPersistentBindings() -} - -// UpdateLoop is expected to internally call Update() -// or do something equivalent at the given frameRate, -// sending signals to the sceneCh after each Update(). -// Any flushing should be done as needed. This should -// not be called with `go`, if this requires goroutines -// it should create them itself. -// UpdateLoop is expected separately from Update() and -// Flush() because it will be more efficient for a Logical -// System to perform its own Updates outside of it’s exposed -// interface. -func (eb *Bus) UpdateLoop(framerate int, updateCh chan struct{}) error { - // The logical loop. - // In order, it waits on receiving a signal to begin a logical frame. - // It then runs any functions bound to when a frame begins. - // It then allows a scene to perform it's loop operation. - eb.framesElapsed = 0 - eb.framerate = framerate - frameDelay := timing.FPSToFrameDelay(framerate) - if eb.Ticker == nil { - eb.Ticker = time.NewTicker(frameDelay) - } - go eb.ResolveChanges() - go func() { - eb.Ticker.Reset(frameDelay) - frameDelayF64 := float64(frameDelay) - lastTick := time.Now() - for { - select { - case now := <-eb.Ticker.C: - deltaTime := now.Sub(lastTick) - lastTick = now - <-eb.TriggerBack(Enter, EnterPayload{ - FramesElapsed: eb.framesElapsed, - SinceLastFrame: deltaTime, - TickPercent: float64(deltaTime) / frameDelayF64, - }) - eb.framesElapsed++ - select { - case updateCh <- struct{}{}: - case <-eb.doneCh: - return - } - case <-eb.doneCh: - return - } - } - }() - return nil -} - -// EnterPayload is the payload sent down to Enter bindings -type EnterPayload struct { - FramesElapsed int - SinceLastFrame time.Duration - TickPercent float64 -} - -// Update updates all entities bound to this handler -func (eb *Bus) Update() error { - <-eb.TriggerBack(Enter, EnterPayload{ - FramesElapsed: eb.framesElapsed, - }) - return nil -} - -// Flush refreshes any changes to the Handler’s bindings. -func (eb *Bus) Flush() error { - if len(eb.unbindAllAndRebinds) > 0 { - eb.resolveUnbindAllAndRebinds() - } - // Specific unbinds - if len(eb.unbinds) > 0 { - eb.resolveUnbinds() - } - - // A full set of unbind settings - if len(eb.fullUnbinds) > 0 { - eb.resolveFullUnbinds() - } - - // A partial set of unbind settings - if len(eb.partUnbinds) > 0 { - eb.resolvePartialUnbinds() - } - - // Bindings - if len(eb.binds) > 0 { - eb.resolveBindings() - } - return nil -} - -// Stop ceases anything spawned by an ongoing UpdateLoop -func (eb *Bus) Stop() error { - if eb.Ticker != nil { - eb.Ticker.Stop() - } - eb.doneCh <- struct{}{} - return nil -} - -// Pause stops the event bus from running any further enter events -func (eb *Bus) Pause() { - eb.Ticker.Reset(math.MaxInt32 * time.Second) -} - -// Resume will resume emitting enter events -func (eb *Bus) Resume() { - eb.Ticker.Reset(timing.FPSToFrameDelay(eb.framerate)) -} - -// FramesElapsed returns how many frames have elapsed since UpdateLoop was last called. -func (eb *Bus) FramesElapsed() int { - return eb.framesElapsed -} - -// SetTick optionally updates the Logical System’s tick rate -// (while it is looping) to be frameRate. If this operation is not -// supported, it should return an error. -func (eb *Bus) SetTick(framerate int) error { - eb.Ticker.Reset(timing.FPSToFrameDelay(framerate)) - return nil -} diff --git a/event/handler_test.go b/event/handler_test.go deleted file mode 100644 index 9c5e0d1b..00000000 --- a/event/handler_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package event - -import ( - "testing" - "time" -) - -func TestHandler(t *testing.T) { - updateCh := make(chan struct{}) - if UpdateLoop(60, updateCh) != nil { - t.Fatalf("UpdateLoop failed") - } - triggers := 0 - Bind(Enter, 0, func(CID, interface{}) int { - triggers++ - return 0 - }) - sleep() - if triggers != 1 { - t.Fatalf("expected update loop to increment triggers") - } - <-updateCh - sleep() - if triggers != 2 { - t.Fatalf("expected update loop to increment triggers") - } - if FramesElapsed() != 2 { - t.Fatalf("expected 2 update frames to have elapsed") - } - if SetTick(1) != nil { - t.Fatalf("SetTick failed") - } - <-updateCh - if Stop() != nil { - t.Fatalf("Stop failed") - } - sleep() - sleep() - select { - case <-updateCh: - t.Fatal("Handler should be closed") - default: - } - expectedTriggers := triggers + 1 - if Update() != nil { - t.Fatalf("Update failed") - } - sleep() - - if triggers != expectedTriggers { - t.Fatalf("expected update to increment triggers") - } - if Flush() != nil { - t.Fatalf("Flush failed") - } - - Flush() - sleep() - if Update() != nil { - t.Fatalf("final Update failed") - } - sleep() - sleep() - Reset() -} - -func BenchmarkHandler(b *testing.B) { - triggers := 0 - entities := 10 - go DefaultBus.ResolveChanges() - for i := 0; i < entities; i++ { - DefaultBus.GlobalBind(Enter, func(CID, interface{}) int { - triggers++ - return 0 - }) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - <-DefaultBus.TriggerBack(Enter, DefaultBus.framesElapsed) - } -} - -func TestPauseAndResume(t *testing.T) { - b := NewBus(nil) - b.ResolveChanges() - triggerCt := 0 - b.Bind("EnterFrame", 0, func(CID, interface{}) int { - triggerCt++ - return 0 - }) - ch := make(chan struct{}, 1000) - b.UpdateLoop(60, ch) - time.Sleep(1 * time.Second) - b.Pause() - time.Sleep(1 * time.Second) - oldCt := triggerCt - time.Sleep(1 * time.Second) - if oldCt != triggerCt { - t.Fatalf("pause did not stop enter frame from triggering: expected %v got %v", oldCt, triggerCt) - } - - b.Resume() - time.Sleep(1 * time.Second) - newCt := triggerCt - if newCt == oldCt { - t.Fatalf("resume did not resume enter frame triggering: expected %v got %v", oldCt, newCt) - } -} diff --git a/event/internal.go b/event/internal.go new file mode 100644 index 00000000..9d1b9a8c --- /dev/null +++ b/event/internal.go @@ -0,0 +1,56 @@ +package event + +import "sync" + +type bindableList map[BindID]UnsafeBindable + +func (bl bindableList) storeBindable(fn UnsafeBindable, bindID BindID) { + bl[bindID] = fn +} + +func (bl bindableList) remove(bindID BindID) { + delete(bl, bindID) +} + +func (eb *Bus) getBindableList(eventID UnsafeEventID, callerID CallerID) bindableList { + if m := eb.bindingMap[eventID]; m == nil { + eb.bindingMap[eventID] = make(map[CallerID]bindableList) + bl := make(bindableList) + eb.bindingMap[eventID][callerID] = bl + return bl + } + bl := eb.bindingMap[eventID][callerID] + if bl == nil { + bl := make(bindableList) + eb.bindingMap[eventID][callerID] = bl + } + return bl +} + +func (bus *Bus) trigger(binds bindableList, eventID UnsafeEventID, callerID CallerID, data interface{}) { + wg := &sync.WaitGroup{} + wg.Add(len(binds)) + for bindID, bnd := range binds { + bindID := bindID + bnd := bnd + if bnd == nil { + wg.Done() + continue + } + go func() { + if callerID == Global || bus.callerMap.HasEntity(callerID) { + response := bnd(callerID, data) + switch response { + case UnbindThis: + // Q: Why does this call bus.Unbind when it already has the event index to delete? + // A: This goroutine does not own a write lock on the bus, and should therefore + // not modify its contents. We do not have a simple way of promoting our read lock + // to a write lock. + bus.Unbind(Binding{EventID: eventID, CallerID: callerID, BindID: bindID}) + } + } + wg.Done() + }() + } + wg.Wait() +} diff --git a/event/resolve.go b/event/resolve.go deleted file mode 100644 index 847d6d82..00000000 --- a/event/resolve.go +++ /dev/null @@ -1,135 +0,0 @@ -package event - -import "time" - -// ResolveChanges is a constant loop that tracks slices of bind or unbind calls -// and resolves them individually such that they don't break the bus. -// Each section of the loop waits for the predetermined refreshrate prior to attempting to flush. -// -// If you ask "Why does this not use select over channels, share memory by communicating", -// the answer is we tried, and it was cripplingly slow. -func (eb *Bus) ResolveChanges() { - eb.init.Do(func() { - go func() { - for { - time.Sleep(eb.refreshRate) - eb.Flush() - } - }() - }) -} - -// SetRefreshRate on the event bus detailing the time to wait per attempt to ResolveChanges. -func (eb *Bus) SetRefreshRate(refreshRate time.Duration) { - eb.refreshRate = refreshRate -} - -func (eb *Bus) resolveUnbindAllAndRebinds() { - eb.mutex.Lock() - eb.pendingMutex.Lock() - for _, ubaarb := range eb.unbindAllAndRebinds { - unbind := ubaarb.ub - orderedBindables := ubaarb.bnds - orderedBindOptions := ubaarb.bs - - var namekeys []string - // If we were given a name, - // we'll just iterate with that name. - if unbind.Name != "" { - namekeys = append(namekeys, unbind.Name) - // Otherwise, iterate through all events. - } else { - for k := range eb.bindingMap { - namekeys = append(namekeys, k) - } - } - - if unbind.CallerID != 0 { - for _, k := range namekeys { - delete(eb.bindingMap[k], unbind.CallerID) - } - } else { - for _, k := range namekeys { - delete(eb.bindingMap, k) - } - } - - // Bindings - for i := 0; i < len(orderedBindables); i++ { - fn := orderedBindables[i] - opt := orderedBindOptions[i] - list := eb.getBindableList(opt) - list.storeBindable(fn) - } - } - eb.unbindAllAndRebinds = []UnbindAllOption{} - eb.pendingMutex.Unlock() - eb.mutex.Unlock() -} - -func (eb *Bus) resolveUnbinds() { - eb.mutex.Lock() - eb.pendingMutex.Lock() - for _, bnd := range eb.unbinds { - eb.getBindableList(bnd.Event).removeBinding(bnd) - } - eb.unbinds = []binding{} - eb.pendingMutex.Unlock() - eb.mutex.Unlock() -} - -func (eb *Bus) resolveFullUnbinds() { - eb.mutex.Lock() - eb.pendingMutex.Lock() - for _, opt := range eb.fullUnbinds { - eb.getBindableList(opt.Event).removeBindable(opt.Fn) - } - eb.fullUnbinds = []UnbindOption{} - eb.pendingMutex.Unlock() - eb.mutex.Unlock() -} - -func (eb *Bus) resolvePartialUnbinds() { - eb.mutex.Lock() - eb.pendingMutex.Lock() - for _, opt := range eb.partUnbinds { - var namekeys []string - - // If we were given a name, - // we'll just iterate with that name. - if opt.Name != "" { - namekeys = append(namekeys, opt.Name) - - // Otherwise, iterate through all events. - } else { - for k := range eb.bindingMap { - namekeys = append(namekeys, k) - } - } - - if opt.CallerID != 0 { - for _, k := range namekeys { - delete(eb.bindingMap[k], opt.CallerID) - } - } else { - for _, k := range namekeys { - delete(eb.bindingMap, k) - } - } - } - eb.partUnbinds = []Event{} - eb.pendingMutex.Unlock() - eb.mutex.Unlock() -} - -func (eb *Bus) resolveBindings() { - eb.mutex.Lock() - eb.pendingMutex.Lock() - for _, bindSet := range eb.binds { - list := eb.getBindableList(bindSet.Event) - list.storeBindable(bindSet.Fn) - } - eb.binds = []UnbindOption{} - eb.pendingMutex.Unlock() - eb.mutex.Unlock() -} diff --git a/event/resolve_test.go b/event/resolve_test.go deleted file mode 100644 index 58cbc97c..00000000 --- a/event/resolve_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package event - -import ( - "testing" - "time" -) - -func TestResolveChangesWithRefreshRate(t *testing.T) { - b := NewBus(nil) - b.SetRefreshRate(6 * time.Second) - b.ResolveChanges() - failed := false - b.Bind("EnterFrame", 0, func(CID, interface{}) int { - failed = true - return 0 - }) - ch := make(chan struct{}, 1000) - b.UpdateLoop(60, ch) - time.Sleep(3 * time.Second) - if failed { - t.Fatal("binding was called before refresh rate should have added binding") - } -} diff --git a/event/response.go b/event/response.go index 336cd389..f3fabcff 100644 --- a/event/response.go +++ b/event/response.go @@ -1,20 +1,14 @@ package event -// Response types from bindables -// reponses are not their own type because func(event.CID, interface{}) int -// is easier to write than func(event.CID, interface{}) event.Response. This may -// yet change. +type Response uint8 + +// Response types for bindables const ( // NoResponse or 0, is returned by events that // don't want the event bus to do anything with // the event after they have been evaluated. This // is the usual behavior. - NoResponse = iota - // UnbindEvent unbinds everything for a specific - // event name from an entity at the bindable's - // priority. - UnbindEvent - // UnbindSingle just unbinds the one binding that - // it is returned from - UnbindSingle + NoResponse Response = iota + // UnbindThis unbinds the one binding that returns it. + UnbindThis ) diff --git a/event/strings.go b/event/strings.go deleted file mode 100644 index 0c4885fa..00000000 --- a/event/strings.go +++ /dev/null @@ -1,50 +0,0 @@ -package event - -// Oak uses the following built in events: -// -// - CollisionStart/Stop: when a PhaseCollision entity starts/stops touching some label. -// Payload: (collision.Label) the label the entity has started/stopped touching -// -// - MouseCollisionStart/Stop: as above, for mouse collision -// Payload: (*mouse.Event) -// -// - Mouse events: MousePress, MouseRelease, MouseScrollDown, MouseScrollUp, MouseDrag -// Payload: (*mouse.Event) details on the mouse event -// -// - KeyDown, KeyDown$a: when any key is pressed down, when key $a is pressed down. -// Payload: (key.Event) the key pressed -// -// - KeyUp, KeyUp$a: when any key is released, when key $a is released. -// Payload: (key.Event) the key released -// -// And the following: -const ( - // Enter : the beginning of every logical frame. - // Payload: (EnterPayload) details on the frame and time since last tick - Enter = "EnterFrame" - // AnimationEnd: Triggered on animations CIDs when they loop from the last to the first frame - // Payload: nil - AnimationEnd = "AnimationEnd" - // ViewportUpdate: Triggered when the position of of the viewport changes - // Payload: intgeom.Point2 - ViewportUpdate = "ViewportUpdate" - // OnStop: Triggered when the engine is stopped. - // Payload: nil - OnStop = "OnStop" - // FocusGain: Triggered when the window gains focus - // Payload: nil - FocusGain = "FocusGain" - // FocusLoss: Triggered when the window loses focus - // Payload: nil - FocusLoss = "FocusLoss" - // InputChange: triggered when the most recent input device changes (e.g. keyboard to joystick or vice versa) - // Payload: oak.InputType - InputChange = "InputChange" -) - -// -// Note all events built in to oak are CapitalizedCamelCase. Although our adding of new -// built in events is rare, we don't consider the addition of these events breaking -// changes for versioning. If a game has many events with generalized names, making -// them uncapitalizedCamelCase is perhaps the best approach to guarantee that builtin -// event names will never conflict with custom events. diff --git a/event/trigger.go b/event/trigger.go index ed283052..66322b21 100644 --- a/event/trigger.go +++ b/event/trigger.go @@ -1,101 +1,42 @@ package event -import ( - "sync" -) - -// TriggerBack is a version of Trigger which returns a channel that -// informs on when all bindables have been called and returned from -// the input event. It is dangerous to use this unless you have a -// very good idea how things will synchronize, as if a triggered -// bindable itself makes a TriggerBack call, this will cause the engine to freeze, -// as the function will never end because the first TriggerBack has control of -// the lock for the event bus, and the first TriggerBack won't give up that lock -// until the function ends. -// -// This inherently means that when you call Trigger, the event will almost -// almost never be immediately triggered but rather will be triggered sometime -// soon in the future. -// -// TriggerBack is right now used by the primary logic loop to dictate logical -// framerate, so EnterFrame events are called through TriggerBack. -func (eb *Bus) TriggerBack(eventName string, data interface{}) chan struct{} { - ch := make(chan struct{}) - go func(ch chan struct{}, eb *Bus, eventName string, data interface{}) { - eb.trigger(eventName, data) - close(ch) - }(ch, eb, eventName, data) - return ch -} - // Trigger will scan through the event bus and call all bindables found attached // to the given event, with the passed in data. -func (eb *Bus) Trigger(eventName string, data interface{}) { - go func(eb *Bus, eventName string, data interface{}) { - eb.trigger(eventName, data) - }(eb, eventName, data) -} - -// TriggerCIDBack acts like trigger back, but triggers for a specific cid only. -func (eb *Bus) TriggerCIDBack(cid CID, eventName string, data interface{}) chan struct{} { +func (bus *Bus) TriggerForCaller(callerID CallerID, eventID UnsafeEventID, data interface{}) chan struct{} { + if callerID == Global { + return bus.Trigger(eventID, data) + } ch := make(chan struct{}) go func() { - eb.mutex.RLock() - if idMap, ok := eb.bindingMap[eventName]; ok { - if bs, ok := idMap[cid]; ok { - eb.triggerDefault(bs.sl, cid, eventName, data) + bus.mutex.RLock() + if idMap, ok := bus.bindingMap[eventID]; ok { + if bs, ok := idMap[callerID]; ok { + bus.trigger(bs, eventID, callerID, data) } } - eb.mutex.RUnlock() + bus.mutex.RUnlock() close(ch) }() return ch } -func (eb *Bus) trigger(eventName string, data interface{}) { - eb.mutex.RLock() - for id, bs := range eb.bindingMap[eventName] { - if bs != nil { - eb.triggerDefault(bs.sl, id, eventName, data) +func (bus *Bus) Trigger(eventID UnsafeEventID, data interface{}) chan struct{} { + ch := make(chan struct{}) + go func() { + bus.mutex.RLock() + for callerID, bs := range bus.bindingMap[eventID] { + bus.trigger(bs, eventID, callerID, data) } - } - eb.mutex.RUnlock() + bus.mutex.RUnlock() + close(ch) + }() + return ch } -func (eb *Bus) triggerDefault(sl []Bindable, id CID, eventName string, data interface{}) { - prog := &sync.WaitGroup{} - prog.Add(len(sl)) - for i, bnd := range sl { - if bnd == nil { - prog.Done() - continue - } - go func(bnd Bindable, id CID, eventName string, data interface{}, prog *sync.WaitGroup, index int) { - eb.handleBindable(bnd, id, data, index, eventName) - prog.Done() - }(bnd, id, eventName, data, prog, i) - } - prog.Wait() +func TriggerOn[T any](b Handler, ev EventID[T], data T) chan struct{} { + return b.Trigger(ev.UnsafeEventID, data) } -func (eb *Bus) handleBindable(bnd Bindable, id CID, data interface{}, index int, eventName string) { - if id == 0 || eb.callerMap.HasEntity(id) { - response := bnd(id, data) - switch response { - case UnbindEvent: - UnbindAll(Event{ - Name: eventName, - CallerID: id, - }) - case UnbindSingle: - bnd := binding{ - Event: Event{ - Name: eventName, - CallerID: id, - }, - index: index, - } - bnd.unbind(eb) - } - } +func TriggerForCallerOn[T any](b Handler, cid CallerID, ev EventID[T], data T) chan struct{} { + return b.TriggerForCaller(cid, ev.UnsafeEventID, data) } diff --git a/event/unbind.go b/event/unbind.go deleted file mode 100644 index 636d465a..00000000 --- a/event/unbind.go +++ /dev/null @@ -1,50 +0,0 @@ -package event - -// Unbind on a binding is a rewriting of bus.Unbind(b) -func (b binding) unbind(eb *Bus) { - eb.unbind(b) -} - -func (eb *Bus) unbind(b binding) { - eb.pendingMutex.Lock() - eb.unbinds = append(eb.unbinds, b) - eb.pendingMutex.Unlock() -} - -// UnbindAllAndRebind is a way to reset the bindings on a CID efficiently, -// given a new set of equal length binding and event slices. This is equivalent -// to calling UnbindAll and then looping over Bind calls for the pairs of -// bindables and event names, but uses less mutex time. -func (eb *Bus) UnbindAllAndRebind(bo Event, binds []Bindable, cid CID, events []string) { - opts := make([]Event, len(events)) - for k, v := range events { - opts[k] = Event{ - Name: v, - CallerID: cid, - } - } - - eb.pendingMutex.Lock() - eb.unbindAllAndRebinds = append(eb.unbindAllAndRebinds, UnbindAllOption{ - ub: bo, - bs: opts, - bnds: binds, - }) - eb.pendingMutex.Unlock() -} - -// UnbindAll removes all events that match the given bindingOption from the -// default event bus -func (eb *Bus) UnbindAll(opt Event) { - eb.pendingMutex.Lock() - eb.partUnbinds = append(eb.partUnbinds, opt) - eb.pendingMutex.Unlock() -} - -// UnbindBindable is a manual way to unbind a function Bindable. Use of -// this with closures will result in undefined behavior. -func (eb *Bus) UnbindBindable(opt UnbindOption) { - eb.pendingMutex.Lock() - eb.fullUnbinds = append(eb.fullUnbinds, opt) - eb.pendingMutex.Unlock() -} From 71c28e1e5c90eb91488871c67ca667c1c6101484 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 19 Mar 2022 09:28:39 -0500 Subject: [PATCH 02/41] oak: use new event paradigms in key, mouse, joystick, and oak --- .gitignore | 5 ++- collision/space.go | 2 +- config.go | 5 --- event/bus.go | 2 +- event/caller.go | 16 +++++---- event/default.go | 12 +++++++ event/events.go | 2 -- go.mod | 2 +- init.go | 3 -- inputLoop.go | 50 ++++++++++++++------------ inputTracker.go | 43 +++++++++++++--------- joystick/joystick.go | 86 +++++++++++++++++++++++++++++--------------- key/events.go | 60 +++++++++++++++++++++++-------- mouse/bindings.go | 17 --------- mouse/event.go | 21 +++++------ mouse/events.go | 77 +++++++++++++++++++++++++++++++++++++++ mouse/mouse.go | 3 +- mouse/onCollision.go | 67 ++++++++++++++++++---------------- mouse/strings.go | 19 ---------- sceneLoop.go | 15 +++----- viewport.go | 2 +- window.go | 79 ++++++++++++++++++++-------------------- 22 files changed, 353 insertions(+), 235 deletions(-) create mode 100644 event/default.go delete mode 100644 mouse/bindings.go create mode 100644 mouse/events.go delete mode 100644 mouse/strings.go diff --git a/.gitignore b/.gitignore index 39182116..1415900b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,7 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -coverage.txt \ No newline at end of file +coverage.txt + +# Workspace configuration +.vscode \ No newline at end of file diff --git a/collision/space.go b/collision/space.go index 1d9c07ac..6d54da3f 100644 --- a/collision/space.go +++ b/collision/space.go @@ -23,7 +23,7 @@ type Space struct { Label Label // A CID can be used to get the exact // entity which this rectangle belongs to. - CID event.CID + CID event.CallerID // Type represents which ID space the above ID // corresponds to. Type int diff --git a/config.go b/config.go index 57c718e5..c44d9856 100644 --- a/config.go +++ b/config.go @@ -22,7 +22,6 @@ type Config struct { IdleDrawFrameRate int `json:"idleDrawFrameRate"` Language string `json:"language"` Title string `json:"title"` - EventRefreshRate Duration `json:"refreshRate"` BatchLoad bool `json:"batchLoad"` GestureSupport bool `json:"gestureSupport"` LoadBuiltinCommands bool `json:"loadBuiltinCommands"` @@ -97,7 +96,6 @@ func (c Config) setDefaults() Config { c.IdleDrawFrameRate = 60 c.Language = "English" c.Title = "Oak Window" - c.EventRefreshRate = Duration(50 * time.Millisecond) return c } @@ -222,9 +220,6 @@ func (c Config) overwriteFrom(c2 Config) Config { if c2.Title != "" { c.Title = c2.Title } - if c2.EventRefreshRate != 0 { - c.EventRefreshRate = c2.EventRefreshRate - } // Booleans can be directly overwritten-- all booleans in a Config // default to false, if they were unset they will stay false. c.BatchLoad = c2.BatchLoad diff --git a/event/bus.go b/event/bus.go index ca8a59f9..297d10a8 100644 --- a/event/bus.go +++ b/event/bus.go @@ -118,4 +118,4 @@ func (bus *Bus) SetEnterLoopRate(frameDelay time.Duration) error { } bus.ticker.Reset(frameDelay) return nil -} +} \ No newline at end of file diff --git a/event/caller.go b/event/caller.go index 4f26e694..9c4b59f0 100644 --- a/event/caller.go +++ b/event/caller.go @@ -5,12 +5,12 @@ import ( "sync/atomic" ) -// A CID is a caller ID that entities use to trigger and bind functionality +// A CallerID is a caller ID that entities use to trigger and bind functionality type CallerID int64 const Global CallerID = 0 -// A CallerMap tracks CID mappings to Entities. +// A CallerMap tracks CallerID mappings to Entities. // This is an alternative to passing in the entity via closure scoping, // and allows for more general bindings as simple top level functions. type CallerMap struct { @@ -28,10 +28,6 @@ func NewCallerMap() *CallerMap { } } -// DefaultCallerMap is the caller map used by all event package caller -// functions. -var DefaultCallerMap = NewCallerMap() - // NextID finds the next available caller id // and returns it, after adding the given entity to // the caller map. @@ -66,3 +62,11 @@ func (cm *CallerMap) DestroyEntity(id CallerID) { delete(cm.callers, id) cm.callersLock.Unlock() } + +// Reset clears the caller map to forget all registered callers. +func (cm *CallerMap) Reset() { + cm.callersLock.Lock() + *cm.highestID = 0 + cm.callers = map[CallerID]interface{}{} + cm.callersLock.Unlock() +} diff --git a/event/default.go b/event/default.go new file mode 100644 index 00000000..fdd1015f --- /dev/null +++ b/event/default.go @@ -0,0 +1,12 @@ +package event + +var DefaultBus *Bus + +// DefaultCallerMap is the caller map used by all event package caller +// functions. +var DefaultCallerMap *CallerMap + +func init() { + DefaultCallerMap = NewCallerMap() + DefaultBus = NewBus(DefaultCallerMap) +} diff --git a/event/events.go b/event/events.go index e8a444bf..45b5a886 100644 --- a/event/events.go +++ b/event/events.go @@ -48,6 +48,4 @@ var ( FocusGain = RegisterEvent[NoPayload]() // FocusLoss: Triggered when the window loses focus FocusLoss = RegisterEvent[NoPayload]() - // InputChange: triggered when the most recent input device changes (e.g. keyboard to joystick or vice versa) - InputChange = RegisterEvent[NoPayload]() ) diff --git a/go.mod b/go.mod index 46e19be6..bef1053c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/oakmound/oak/v3 -go 1.16 +go 1.18 require ( dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037 diff --git a/init.go b/init.go index f96ac657..e2ab5694 100644 --- a/init.go +++ b/init.go @@ -65,9 +65,6 @@ func (w *Window) Init(firstScene string, configOptions ...ConfigOption) error { if w.config.TrackInputChanges { trackJoystickChanges(w.eventHandler) } - if w.config.EventRefreshRate != 0 { - w.eventHandler.SetRefreshRate(time.Duration(w.config.EventRefreshRate)) - } if !w.config.SkipRNGSeed { // seed math/rand with time.Now, useful for minimal examples diff --git a/inputLoop.go b/inputLoop.go index 1f7e814b..04115818 100644 --- a/inputLoop.go +++ b/inputLoop.go @@ -21,27 +21,25 @@ func (w *Window) inputLoop() { switch e.To { case lifecycle.StageDead: dlog.Info(dlog.WindowClosed) - // OnStop needs to be sent through TriggerBack, otherwise the - // program will close before the stop events get propagated. - <-w.eventHandler.TriggerBack(event.OnStop, nil) + <-event.TriggerOn(w.eventHandler, event.OnStop, event.NoPayload{}) close(w.quitCh) return case lifecycle.StageFocused: w.inFocus = true // If you are in focused state, we don't care how you got there w.DrawTicker.Reset(timing.FPSToFrameDelay(w.DrawFrameRate)) - w.eventHandler.Trigger(event.FocusGain, nil) + event.TriggerOn(w.eventHandler, event.FocusGain, event.NoPayload{}) case lifecycle.StageVisible: // If the last state was focused, this means the app is out of focus // otherwise, we're visible for the first time if e.From > e.To { w.inFocus = false w.DrawTicker.Reset(timing.FPSToFrameDelay(w.IdleDrawFrameRate)) - w.eventHandler.Trigger(event.FocusLoss, nil) + event.TriggerOn(w.eventHandler, event.FocusLoss, event.NoPayload{}) } else { w.inFocus = true w.DrawTicker.Reset(timing.FPSToFrameDelay(w.DrawFrameRate)) - w.eventHandler.Trigger(event.FocusGain, nil) + event.TriggerOn(w.eventHandler, event.FocusGain, event.NoPayload{}) } } // Send key events @@ -76,7 +74,7 @@ func (w *Window) inputLoop() { // Mouse events all receive an x, y, and button string. case mouse.Event: button := omouse.Button(e.Button) - eventName := omouse.GetEventName(e.Direction, e.Button) + ev := omouse.GetEvent(e.Direction, e.Button) // The event triggered for mouse events has the same scaling as the // render and collision space. I.e. if the viewport is at 0, the mouse's // position is exactly the same as the position of a visible entity @@ -85,7 +83,7 @@ func (w *Window) inputLoop() { float64((((e.X - float32(w.windowRect.Min.X)) / float32(w.windowRect.Max.X-w.windowRect.Min.X)) * float32(w.ScreenWidth))), float64((((e.Y - float32(w.windowRect.Min.Y)) / float32(w.windowRect.Max.Y-w.windowRect.Min.Y)) * float32(w.ScreenHeight))), button, - eventName, + ev, ) w.TriggerMouseEvent(mevent) @@ -104,8 +102,8 @@ func (w *Window) inputLoop() { func (w *Window) TriggerKeyDown(e okey.Event) { k := e.Code.String()[4:] w.SetDown(k) - w.eventHandler.Trigger(okey.Down, e) - w.eventHandler.Trigger(okey.Down+k, e) + event.TriggerOn(w.eventHandler, okey.AnyDown, e) + event.TriggerOn(w.eventHandler, okey.Down(e.Code), e) } // TriggerKeyUp triggers a software-emulated key release. @@ -115,8 +113,8 @@ func (w *Window) TriggerKeyDown(e okey.Event) { func (w *Window) TriggerKeyUp(e okey.Event) { k := e.Code.String()[4:] w.SetUp(k) - w.eventHandler.Trigger(okey.Up, e) - w.eventHandler.Trigger(okey.Up+k, e) + event.TriggerOn(w.eventHandler, okey.AnyUp, e) + event.TriggerOn(w.eventHandler, okey.Up(e.Code), e) } // TriggerKeyHeld triggers a software-emulated key hold signal. @@ -124,9 +122,8 @@ func (w *Window) TriggerKeyUp(e okey.Event) { // From the perspective of the event handler this is indistinguishable // from a real key hold signal. func (w *Window) TriggerKeyHeld(e okey.Event) { - k := e.Code.String()[4:] - w.eventHandler.Trigger(okey.Held, e) - w.eventHandler.Trigger(okey.Held+k, e) + event.TriggerOn(w.eventHandler, okey.AnyHeld, e) + event.TriggerOn(w.eventHandler, okey.Held(e.Code), e) } // TriggerMouseEvent triggers a software-emulated mouse event. @@ -136,12 +133,21 @@ func (w *Window) TriggerKeyHeld(e okey.Event) { func (w *Window) TriggerMouseEvent(mevent omouse.Event) { w.LastMouseEvent = mevent omouse.LastEvent = mevent - w.Propagate(mevent.Event+"On", mevent) - w.eventHandler.Trigger(mevent.Event, &mevent) + on, onOk := omouse.EventOn(mevent.EventType) + if onOk { + w.Propagate(on, mevent) + } + event.TriggerOn(w.eventHandler, mevent.EventType, &mevent) + + if onOk { + rel, ok := omouse.EventRelative(on) + if ok { + relativeEvent := mevent + relativeEvent.Point2[0] += float64(w.viewPos[0]) + relativeEvent.Point2[1] += float64(w.viewPos[1]) + w.LastRelativeMouseEvent = relativeEvent - relativeEvent := mevent - relativeEvent.Point2[0] += float64(w.viewPos[0]) - relativeEvent.Point2[1] += float64(w.viewPos[1]) - w.LastRelativeMouseEvent = relativeEvent - w.Propagate(relativeEvent.Event+"OnRelative", relativeEvent) + w.Propagate(rel, relativeEvent) + } + } } diff --git a/inputTracker.go b/inputTracker.go index f8cbef27..781096a4 100644 --- a/inputTracker.go +++ b/inputTracker.go @@ -12,33 +12,39 @@ import ( ) // InputType expresses some form of input to the engine to represent a player -type InputType = int32 +type InputType int32 + +// InputChange is triggered when the most recent input device changes (e.g. keyboard to joystick or vice versa) +var InputChange = event.RegisterEvent[InputType]() + +var trackingJoystickChange = event.RegisterEvent[event.NoPayload]() // Supported Input Types const ( - InputKeyboardMouse InputType = iota - InputJoystick InputType = iota + InputKeyboard InputType = iota + InputMouse + InputJoystick ) func (w *Window) trackInputChanges() { - w.eventHandler.GlobalBind(key.Down, func(event.CID, interface{}) int { - old := atomic.SwapInt32(&w.mostRecentInput, InputKeyboardMouse) - if old != InputKeyboardMouse { - w.eventHandler.Trigger(event.InputChange, InputKeyboardMouse) + event.Bind(w.eventHandler, key.AnyDown, event.Global, func(event.CallerID, key.Event) event.Response { + old := atomic.SwapInt32(&w.mostRecentInput, int32(InputKeyboard)) + if InputType(old) != InputKeyboard { + event.TriggerOn(w.eventHandler, InputChange, InputKeyboard) } return 0 }) - w.eventHandler.GlobalBind(mouse.Press, func(event.CID, interface{}) int { - old := atomic.SwapInt32(&w.mostRecentInput, InputKeyboardMouse) - if old != InputKeyboardMouse { - w.eventHandler.Trigger(event.InputChange, InputKeyboardMouse) + event.Bind(w.eventHandler, mouse.Press, event.Global, func(event.CallerID, *mouse.Event) event.Response { + old := atomic.SwapInt32(&w.mostRecentInput, int32(InputMouse)) + if InputType(old) != InputMouse { + event.TriggerOn(w.eventHandler, InputChange, InputMouse) } return 0 }) - w.eventHandler.GlobalBind("Tracking"+joystick.Change, func(event.CID, interface{}) int { - old := atomic.SwapInt32(&w.mostRecentInput, InputJoystick) - if old != InputJoystick { - w.eventHandler.Trigger(event.InputChange, InputJoystick) + event.Bind(w.eventHandler, trackingJoystickChange, event.Global, func(event.CallerID, event.NoPayload) event.Response { + old := atomic.SwapInt32(&w.mostRecentInput, int32(InputMouse)) + if InputType(old) != InputJoystick { + event.TriggerOn(w.eventHandler, InputChange, InputJoystick) } return 0 }) @@ -48,8 +54,11 @@ type joyHandler struct { handler event.Handler } -func (jh *joyHandler) Trigger(ev string, state interface{}) { - jh.handler.Trigger("Tracking"+ev, state) +func (jh *joyHandler) Trigger(eventID event.UnsafeEventID, data interface{}) chan struct{} { + jh.handler.Trigger(trackingJoystickChange.UnsafeEventID, event.NoPayload{}) + ch := make(chan struct{}) + close(ch) + return ch } func trackJoystickChanges(handler event.Handler) { diff --git a/joystick/joystick.go b/joystick/joystick.go index 4632bb35..f2ed83ef 100644 --- a/joystick/joystick.go +++ b/joystick/joystick.go @@ -4,9 +4,11 @@ package joystick import ( "math" + "sync" "time" "github.com/oakmound/oak/v3/dlog" + "github.com/oakmound/oak/v3/event" ) type Input string @@ -30,15 +32,15 @@ const ( ) // Events. All events include a *State payload. -const ( - Change = "JoystickChange" - ButtonDown = "ButtonDown" - ButtonUp = "ButtonUp" - RtTriggerChange = "RtTriggerChange" - LtTriggerChange = "LtTriggerChange" - RtStickChange = "RtStickChange" - LtStickChange = "LtStickChange" - Disconnected = "JoystickDisconnected" +var ( + Change = event.RegisterEvent[*State]() + ButtonDown = event.RegisterEvent[*State]() + ButtonUp = event.RegisterEvent[*State]() + RtTriggerChange = event.RegisterEvent[*State]() + LtTriggerChange = event.RegisterEvent[*State]() + RtStickChange = event.RegisterEvent[*State]() + LtStickChange = event.RegisterEvent[*State]() + Disconnected = event.RegisterEvent[*State]() ) // Init calls any os functions necessary to detect joysticks @@ -49,7 +51,7 @@ func Init() error { // A Triggerer can either be an event bus or event CID, allowing // joystick triggers to be listened to globally or sent to particular entities. type Triggerer interface { - Trigger(string, interface{}) + Trigger(eventID event.UnsafeEventID, data interface{}) chan struct{} } // A Joystick represents a (usually) physical controller connected to the machine. @@ -121,7 +123,7 @@ func (lo *ListenOptions) sendFn() func(Triggerer, *State, *State) { var fn func(Triggerer, *State, *State) if lo.JoystickChanges { fn = func(h Triggerer, cur, last *State) { - h.Trigger(Change, cur) + h.Trigger(Change.UnsafeEventID, cur) } } if lo.GenericButtonPresses { @@ -134,13 +136,13 @@ func (lo *ListenOptions) sendFn() func(Triggerer, *State, *State) { for k, v := range cur.Buttons { if v != last.Buttons[k] { if v && !downTriggered { - h.Trigger(ButtonDown, cur) + h.Trigger(ButtonDown.UnsafeEventID, cur) downTriggered = true if upTriggered { return } } else if !v && !upTriggered { - h.Trigger(ButtonUp, cur) + h.Trigger(ButtonUp.UnsafeEventID, cur) upTriggered = true if downTriggered { return @@ -156,13 +158,13 @@ func (lo *ListenOptions) sendFn() func(Triggerer, *State, *State) { for k, v := range cur.Buttons { if v != last.Buttons[k] { if v && !downTriggered { - h.Trigger(ButtonDown, cur) + h.Trigger(ButtonDown.UnsafeEventID, cur) downTriggered = true if upTriggered { return } } else if !v && !upTriggered { - h.Trigger(ButtonUp, cur) + h.Trigger(ButtonUp.UnsafeEventID, cur) upTriggered = true if downTriggered { return @@ -181,9 +183,9 @@ func (lo *ListenOptions) sendFn() func(Triggerer, *State, *State) { for k, v := range cur.Buttons { if v != last.Buttons[k] { if v { - h.Trigger(k+ButtonDown, cur) + h.Trigger(Down(k).UnsafeEventID, cur) } else { - h.Trigger(k+ButtonUp, cur) + h.Trigger(Up(k).UnsafeEventID, cur) } } } @@ -193,9 +195,9 @@ func (lo *ListenOptions) sendFn() func(Triggerer, *State, *State) { for k, v := range cur.Buttons { if v != last.Buttons[k] { if v { - h.Trigger(k+ButtonDown, cur) + h.Trigger(Down(k).UnsafeEventID, cur) } else { - h.Trigger(k+ButtonUp, cur) + h.Trigger(Up(k).UnsafeEventID, cur) } } } @@ -209,22 +211,22 @@ func (lo *ListenOptions) sendFn() func(Triggerer, *State, *State) { prevFn(h, cur, last) if deltaExceedsThreshold(cur.StickLX, last.StickLX, lo.StickDeadzoneLX) || deltaExceedsThreshold(cur.StickLY, last.StickLY, lo.StickDeadzoneLY) { - h.Trigger(LtStickChange, cur) + h.Trigger(LtStickChange.UnsafeEventID, cur) } if deltaExceedsThreshold(cur.StickRX, last.StickRX, lo.StickDeadzoneRX) || deltaExceedsThreshold(cur.StickRY, last.StickRY, lo.StickDeadzoneRY) { - h.Trigger(RtStickChange, cur) + h.Trigger(RtStickChange.UnsafeEventID, cur) } } } else { fn = func(h Triggerer, cur, last *State) { if deltaExceedsThreshold(cur.StickLX, last.StickLX, lo.StickDeadzoneLX) || deltaExceedsThreshold(cur.StickLY, last.StickLY, lo.StickDeadzoneLY) { - h.Trigger(LtStickChange, cur) + h.Trigger(LtStickChange.UnsafeEventID, cur) } if deltaExceedsThreshold(cur.StickRX, last.StickRX, lo.StickDeadzoneRX) || deltaExceedsThreshold(cur.StickRY, last.StickRY, lo.StickDeadzoneRY) { - h.Trigger(RtStickChange, cur) + h.Trigger(RtStickChange.UnsafeEventID, cur) } } } @@ -235,19 +237,19 @@ func (lo *ListenOptions) sendFn() func(Triggerer, *State, *State) { fn = func(h Triggerer, cur, last *State) { prevFn(h, cur, last) if cur.TriggerL != last.TriggerL { - h.Trigger(LtTriggerChange, cur) + h.Trigger(LtTriggerChange.UnsafeEventID, cur) } if cur.TriggerR != last.TriggerR { - h.Trigger(RtTriggerChange, cur) + h.Trigger(RtTriggerChange.UnsafeEventID, cur) } } } else { fn = func(h Triggerer, cur, last *State) { if cur.TriggerL != last.TriggerL { - h.Trigger(LtTriggerChange, cur) + h.Trigger(LtTriggerChange.UnsafeEventID, cur) } if cur.TriggerR != last.TriggerR { - h.Trigger(RtTriggerChange, cur) + h.Trigger(RtTriggerChange.UnsafeEventID, cur) } } } @@ -255,6 +257,34 @@ func (lo *ListenOptions) sendFn() func(Triggerer, *State, *State) { return fn } +var upEventsLock sync.Mutex +var upEvents = map[string]event.EventID[*State]{} + +func Up(s string) event.EventID[*State] { + upEventsLock.Lock() + defer upEventsLock.Unlock() + if ev, ok := upEvents[s]; ok { + return ev + } + ev := event.RegisterEvent[*State]() + upEvents[s] = ev + return ev +} + +var downEventsLock sync.Mutex +var downEvents = map[string]event.EventID[*State]{} + +func Down(s string) event.EventID[*State] { + downEventsLock.Lock() + defer downEventsLock.Unlock() + if ev, ok := downEvents[s]; ok { + return ev + } + ev := event.RegisterEvent[*State]() + downEvents[s] = ev + return ev +} + func deltaExceedsThreshold(old, new, threshold int16) bool { return intAbs(old-new) > threshold } @@ -306,7 +336,7 @@ func (j *Joystick) Listen(opts *ListenOptions) (cancel func()) { } state, err := j.GetState() if err != nil { - j.Handler.Trigger(Disconnected, j.id) + j.Handler.Trigger(Disconnected.UnsafeEventID, j.id) dlog.Error(err) t.Stop() j.Close() diff --git a/key/events.go b/key/events.go index ec96bd36..12e2e62b 100644 --- a/key/events.go +++ b/key/events.go @@ -1,20 +1,22 @@ package key import ( + "sync" + "github.com/oakmound/oak/v3/event" "golang.org/x/mobile/event/key" ) -const ( +var ( // Down is sent when a key is pressed. It is sent both as // Down, and as Down + the key name. - Down = "KeyDown" + AnyDown = event.RegisterEvent[Event]() // Up is sent when a key is released. It is sent both as // Up, and as Up + the key name. - Up = "KeyUp" + AnyUp = event.RegisterEvent[Event]() // Held is sent when a key is held down. It is sent both as // Held, and as Held + the key name. - Held = "KeyHeld" + AnyHeld = event.RegisterEvent[Event]() ) // An Event is sent as the payload for all key bindings. @@ -23,16 +25,44 @@ type Event = key.Event // A code is a unique integer code for a given common key type Code = key.Code -// Binding will convert a function that accepts a typecast key.Event into a generic event binding -// -// Example: -// bus.Bind(key.Down, key.Binding(keyHandler)) -func Binding(fn func(event.CID, Event) int) func(event.CID, interface{}) int { - return func(cid event.CID, iface interface{}) int { - ke, ok := iface.(Event) - if !ok { - return event.UnbindSingle - } - return fn(cid, ke) +var upEventsLock sync.Mutex +var upEvents = map[Code]event.EventID[Event]{} + +func Up(code Code) event.EventID[Event] { + upEventsLock.Lock() + defer upEventsLock.Unlock() + if ev, ok := upEvents[code]; ok { + return ev + } + ev := event.RegisterEvent[Event]() + upEvents[code] = ev + return ev +} + +var downEventsLock sync.Mutex +var downEvents = map[Code]event.EventID[Event]{} + +func Down(code Code) event.EventID[Event] { + downEventsLock.Lock() + defer downEventsLock.Unlock() + if ev, ok := downEvents[code]; ok { + return ev + } + ev := event.RegisterEvent[Event]() + downEvents[code] = ev + return ev +} + +var heldEventsLock sync.Mutex +var heldEvents = map[Code]event.EventID[Event]{} + +func Held(code Code) event.EventID[Event] { + heldEventsLock.Lock() + defer heldEventsLock.Unlock() + if ev, ok := heldEvents[code]; ok { + return ev } + ev := event.RegisterEvent[Event]() + heldEvents[code] = ev + return ev } diff --git a/mouse/bindings.go b/mouse/bindings.go deleted file mode 100644 index cedfe8dd..00000000 --- a/mouse/bindings.go +++ /dev/null @@ -1,17 +0,0 @@ -package mouse - -import "github.com/oakmound/oak/v3/event" - -// Binding will convert a function that accepts a typecast *mouse.Event into a generic event binding -// -// Example: -// bus.Bind(mouse.ClickOn, mouse.Binding(clickHandler)) -func Binding(fn func(event.CID, *Event) int) func(event.CID, interface{}) int { - return func(cid event.CID, iface interface{}) int { - me, ok := iface.(*Event) - if !ok { - return event.UnbindSingle - } - return fn(cid, me) - } -} diff --git a/mouse/event.go b/mouse/event.go index c185bc7d..b17cbf06 100644 --- a/mouse/event.go +++ b/mouse/event.go @@ -3,17 +3,17 @@ package mouse import ( "github.com/oakmound/oak/v3/alg/floatgeom" "github.com/oakmound/oak/v3/collision" + "github.com/oakmound/oak/v3/event" ) var ( // LastEvent is the last triggered mouse event, // tracked for continuous mouse responsiveness on events // that don't take in a mouse event - LastEvent = NewZeroEvent(0, 0) + LastEvent = Event{} // LastPress is the last triggered mouse event, // where the mouse event was a press. - // If TrackMouseClicks is set to false then this will not be tracked - LastPress = NewZeroEvent(0, 0) + LastPress = Event{} ) // An Event is passed in through all Mouse related event bindings to @@ -22,7 +22,7 @@ var ( type Event struct { floatgeom.Point2 Button - Event string + EventType event.EventID[*Event] // Set StopPropagation on a mouse event to prevent it from triggering on // lower layers of mouse collision spaces while in flight @@ -30,19 +30,14 @@ type Event struct { } // NewEvent creates an event. -func NewEvent(x, y float64, button Button, event string) Event { +func NewEvent(x, y float64, button Button, ev event.EventID[*Event]) Event { return Event{ - Point2: floatgeom.Point2{x, y}, - Button: button, - Event: event, + Point2: floatgeom.Point2{x, y}, + Button: button, + EventType: ev, } } -// NewZeroEvent creates an event with no button or event name. -func NewZeroEvent(x, y float64) Event { - return NewEvent(x, y, ButtonNone, "") -} - // ToSpace converts a mouse event into a collision space func (e Event) ToSpace() *collision.Space { sp := collision.NewUnassignedSpace(e.X(), e.Y(), 0.1, 0.1) diff --git a/mouse/events.go b/mouse/events.go new file mode 100644 index 00000000..8ed4832f --- /dev/null +++ b/mouse/events.go @@ -0,0 +1,77 @@ +package mouse + +import "github.com/oakmound/oak/v3/event" + +var ( + // Press is triggered when a mouse key is pressed down + Press = event.RegisterEvent[*Event]() + // Release is triggered when a mouse key, pressed, is released + Release = event.RegisterEvent[*Event]() + // ScrollDown is triggered when a mouse's scroll wheel scrolls downward + ScrollDown = event.RegisterEvent[*Event]() + // ScrollUp is triggered when a mouse's scroll wheel scrolls upward + ScrollUp = event.RegisterEvent[*Event]() + // Click is triggered when a Release follows a press for the same mouse key without + // other mouse key presses intertwining. + Click = event.RegisterEvent[*Event]() + // Drag is triggered when the mouse is moved. + Drag = event.RegisterEvent[*Event]() + + // The 'On' Variants of all mouse events are triggered when a mouse event occurs on + // a specific entity in a mouse collision tree. + PressOn = event.RegisterEvent[*Event]() + ReleaseOn = event.RegisterEvent[*Event]() + ScrollDownOn = event.RegisterEvent[*Event]() + ScrollUpOn = event.RegisterEvent[*Event]() + ClickOn = event.RegisterEvent[*Event]() + DragOn = event.RegisterEvent[*Event]() + + // Relative variants are like 'On' variants, but their mouse position data is relative to + // the window's current viewport. E.g. if the viewport is at 100,100 and a click happens at + // 100,100 on the window-- Relative will report 100,100, and non-relative will report 200,200. + // TODO: re-evaluate relative vs non-relative mouse events + RelativePressOn = event.RegisterEvent[*Event]() + RelativeReleaseOn = event.RegisterEvent[*Event]() + RelativeScrollDownOn = event.RegisterEvent[*Event]() + RelativeScrollUpOn = event.RegisterEvent[*Event]() + RelativeClickOn = event.RegisterEvent[*Event]() + RelativeDragOn = event.RegisterEvent[*Event]() +) + +// EventOn converts a generic positioned mouse event into its variant indicating +// it occurred on a CallerID targetted entity +func EventOn(ev event.EventID[*Event]) (event.EventID[*Event], bool) { + switch ev { + case Press: + return PressOn, true + case Release: + return ReleaseOn, true + case ScrollDown: + return ScrollDownOn, true + case ScrollUp: + return ScrollUpOn, true + case Click: + return ClickOn, true + case Drag: + return DragOn, true + } + return event.EventID[*Event]{}, false +} + +func EventRelative(ev event.EventID[*Event]) (event.EventID[*Event], bool) { + switch ev { + case PressOn: + return RelativePressOn, true + case ReleaseOn: + return RelativeReleaseOn, true + case ScrollDownOn: + return RelativeScrollDownOn, true + case ScrollUpOn: + return RelativeScrollUpOn, true + case ClickOn: + return RelativeClickOn, true + case DragOn: + return RelativeDragOn, true + } + return event.EventID[*Event]{}, false +} diff --git a/mouse/mouse.go b/mouse/mouse.go index 7fcd47c1..5caa8498 100644 --- a/mouse/mouse.go +++ b/mouse/mouse.go @@ -1,6 +1,7 @@ package mouse import ( + "github.com/oakmound/oak/v3/event" "golang.org/x/mobile/event/mouse" ) @@ -21,7 +22,7 @@ const ( ) // GetEventName returns a string event name given some mobile/mouse information -func GetEventName(d mouse.Direction, b mouse.Button) string { +func GetEvent(d mouse.Direction, b mouse.Button) event.EventID[*Event] { switch d { case mouse.DirPress: return Press diff --git a/mouse/onCollision.go b/mouse/onCollision.go index 31af0bde..5e42cbae 100644 --- a/mouse/onCollision.go +++ b/mouse/onCollision.go @@ -11,6 +11,7 @@ import ( // enable PhaseCollision on the struct. See PhaseCollision. type CollisionPhase struct { OnCollisionS *collision.Space + CallerMap *event.CallerMap LastEvent *Event wasTouching bool @@ -27,50 +28,56 @@ type collisionPhase interface { // PhaseCollision binds to the entity behind the space's CID so that it will // receive MouseCollisionStart and MouseCollisionStop events, appropriately when // the mouse begins to hover or stops hovering over the input space. -func PhaseCollision(s *collision.Space) error { - en := s.CID.E() +func PhaseCollision(s *collision.Space, callerMap *event.CallerMap, handler event.Handler) error { + if callerMap == nil { + callerMap = event.DefaultCallerMap + } + en := callerMap.GetEntity(s.CID) if cp, ok := en.(collisionPhase); ok { oc := cp.getCollisionPhase() oc.OnCollisionS = s - s.CID.Bind(event.Enter, phaseCollisionEnter) + oc.CallerMap = callerMap + event.Bind(handler, event.Enter, s.CID, phaseCollisionEnter(callerMap, handler)) return nil } return errors.New("This space's entity does not implement collisionPhase") } // MouseCollisionStart/Stop: see collision Start/Stop, for mouse collision -// Payload: (*mouse.Event) -const ( - Start = "MouseCollisionStart" - Stop = "MouseCollisionStop" +var ( + Start = event.RegisterEvent[*Event]() + Stop = event.RegisterEvent[*Event]() ) -func phaseCollisionEnter(id event.CID, nothing interface{}) int { - e := id.E().(collisionPhase) - oc := e.getCollisionPhase() - if oc == nil || oc.OnCollisionS == nil { - return 0 - } +func phaseCollisionEnter(callerMap *event.CallerMap, handler event.Handler) func(id event.CallerID, payload event.EnterPayload) event.Response { + return func(id event.CallerID, payload event.EnterPayload) event.Response { - // TODO: think about how this can more cleanly work with multiple controllers - ev := oc.LastEvent - if ev == nil { - ev = &LastEvent - } - if ev.StopPropagation { - return 0 - } + e := callerMap.GetEntity(id).(collisionPhase) + oc := e.getCollisionPhase() + if oc == nil || oc.OnCollisionS == nil { + return 0 + } - if oc.OnCollisionS.Contains(ev.ToSpace()) { - if !oc.wasTouching { - id.Trigger(Start, ev) - oc.wasTouching = true + // TODO: think about how this can more cleanly work with multiple windows + ev := oc.LastEvent + if ev == nil { + ev = &LastEvent } - } else { - if oc.wasTouching { - id.Trigger(Stop, ev) - oc.wasTouching = false + if ev.StopPropagation { + return 0 } + + if oc.OnCollisionS.Contains(ev.ToSpace()) { + if !oc.wasTouching { + event.TriggerForCallerOn(handler, id, Start, ev) + oc.wasTouching = true + } + } else { + if oc.wasTouching { + event.TriggerForCallerOn(handler, id, Stop, ev) + oc.wasTouching = false + } + } + return 0 } - return 0 } diff --git a/mouse/strings.go b/mouse/strings.go deleted file mode 100644 index cdc1c24e..00000000 --- a/mouse/strings.go +++ /dev/null @@ -1,19 +0,0 @@ -package mouse - -// Mouse events: MousePress, MouseRelease, MouseScrollDown, MouseScrollUp, MouseDrag -// Payload: (*mouse.Event) details of the mouse event -const ( - Press = "MousePress" - Release = "MouseRelease" - ScrollDown = "MouseScrollDown" - ScrollUp = "MouseScrollUp" - Click = "MouseClick" - Drag = "MouseDrag" - // - PressOn = Press + "On" - ReleaseOn = Release + "On" - ScrollDownOn = ScrollDown + "On" - ScrollUpOn = ScrollUp + "On" - ClickOn = Click + "On" - DragOn = Drag + "On" -) diff --git a/sceneLoop.go b/sceneLoop.go index e4a31e8c..c2fe80dc 100644 --- a/sceneLoop.go +++ b/sceneLoop.go @@ -5,9 +5,9 @@ import ( "github.com/oakmound/oak/v3/alg/intgeom" "github.com/oakmound/oak/v3/dlog" - "github.com/oakmound/oak/v3/event" "github.com/oakmound/oak/v3/oakerr" "github.com/oakmound/oak/v3/scene" + "github.com/oakmound/oak/v3/timing" ) // the oak loading scene is a reserved scene @@ -88,7 +88,7 @@ func (w *Window) sceneLoop(first string, trackingInputs bool) { dlog.Info(dlog.SceneLooping) cont := true - dlog.ErrorCheck(w.eventHandler.UpdateLoop(w.FrameRate, w.sceneCh)) + w.eventHandler.EnterLoop(timing.FPSToFrameDelay(w.FrameRate)) nextSceneOverride := "" @@ -124,15 +124,8 @@ func (w *Window) sceneLoop(first string, trackingInputs bool) { // be triggered and attempt to access an entity w.CollisionTree.Clear() w.MouseTree.Clear() - if w.CallerMap == event.DefaultCallerMap { - event.ResetCallerMap() - w.CallerMap = event.DefaultCallerMap - } else { - w.CallerMap = event.NewCallerMap() - } - if cmr, ok := w.eventHandler.(event.CallerMapper); ok { - cmr.SetCallerMap(w.CallerMap) - } + w.CallerMap.Reset() + w.eventHandler.SetCallerMap(w.CallerMap) w.DrawStack.Clear() w.DrawStack.PreDraw() diff --git a/viewport.go b/viewport.go index 0cf386f6..9df73069 100644 --- a/viewport.go +++ b/viewport.go @@ -34,7 +34,7 @@ func (w *Window) setViewport(pt intgeom.Point2) { } else { w.viewPos = pt } - w.eventHandler.Trigger(event.ViewportUpdate, w.viewPos) + event.TriggerOn(w.eventHandler, event.ViewportUpdate, w.viewPos) } // GetViewportBounds reports what bounds the viewport has been set to, if any. diff --git a/window.go b/window.go index 93c38069..06d434db 100644 --- a/window.go +++ b/window.go @@ -152,14 +152,13 @@ type Window struct { config Config - mostRecentInput InputType + mostRecentInput int32 exitError error ParentContext context.Context - TrackMouseClicks bool - startupLoading bool - useViewBounds bool + startupLoading bool + useViewBounds bool // UseAspectRatio determines whether new window changes will distort or // maintain the relative width to height ratio of the screen buffer. UseAspectRatio bool @@ -194,7 +193,6 @@ func NewWindow() *Window { c.CollisionTree = collision.DefaultTree c.CallerMap = event.DefaultCallerMap c.DrawStack = render.GlobalDrawStack - c.TrackMouseClicks = true c.commands = make(map[string]func([]string)) c.ControllerID = atomic.AddInt32(nextControllerID, 1) c.ParentContext = context.Background() @@ -202,55 +200,54 @@ func NewWindow() *Window { } // Propagate triggers direct mouse events on entities which are clicked -func (w *Window) Propagate(eventName string, me mouse.Event) { +func (w *Window) Propagate(ev event.EventID[*mouse.Event], me mouse.Event) { hits := w.MouseTree.SearchIntersect(me.ToSpace().Bounds()) sort.Slice(hits, func(i, j int) bool { return hits[i].Location.Min.Z() < hits[i].Location.Max.Z() }) for _, sp := range hits { - <-sp.CID.TriggerBus(eventName, &me, w.eventHandler) + <-event.TriggerForCallerOn(w.eventHandler, sp.CID, ev, &me) if me.StopPropagation { break } } me.StopPropagation = false - if w.TrackMouseClicks { - if eventName == mouse.PressOn+"Relative" { - w.lastRelativePress = me - } else if eventName == mouse.PressOn { - w.LastMousePress = me - } else if eventName == mouse.ReleaseOn { - if me.Button == w.LastMousePress.Button { - pressHits := w.MouseTree.SearchIntersect(w.LastMousePress.ToSpace().Bounds()) - sort.Slice(pressHits, func(i, j int) bool { - return pressHits[i].Location.Min.Z() < pressHits[i].Location.Max.Z() - }) - for _, sp1 := range pressHits { - for _, sp2 := range hits { - if sp1.CID == sp2.CID { - w.eventHandler.Trigger(mouse.Click, &me) - <-sp1.CID.TriggerBus(mouse.ClickOn, &me, w.eventHandler) - if me.StopPropagation { - return - } + if ev == mouse.RelativePressOn { + w.lastRelativePress = me + } else if ev == mouse.PressOn { + w.LastMousePress = me + } else if ev == mouse.ReleaseOn { + if me.Button == w.LastMousePress.Button { + event.TriggerOn(w.eventHandler, mouse.Click, &me) + + pressHits := w.MouseTree.SearchIntersect(w.LastMousePress.ToSpace().Bounds()) + sort.Slice(pressHits, func(i, j int) bool { + return pressHits[i].Location.Min.Z() < pressHits[i].Location.Max.Z() + }) + for _, sp1 := range pressHits { + for _, sp2 := range hits { + if sp1.CID == sp2.CID { + <-event.TriggerForCallerOn(w.eventHandler, sp1.CID, mouse.ClickOn, &me) + if me.StopPropagation { + return } } } } - } else if eventName == mouse.ReleaseOn+"Relative" { - if me.Button == w.lastRelativePress.Button { - pressHits := w.MouseTree.SearchIntersect(w.lastRelativePress.ToSpace().Bounds()) - sort.Slice(pressHits, func(i, j int) bool { - return pressHits[i].Location.Min.Z() < pressHits[i].Location.Max.Z() - }) - for _, sp1 := range pressHits { - for _, sp2 := range hits { - if sp1.CID == sp2.CID { - sp1.CID.Trigger(mouse.ClickOn+"Relative", &me) - if me.StopPropagation { - return - } + } + } else if ev == mouse.RelativeReleaseOn { + if me.Button == w.lastRelativePress.Button { + pressHits := w.MouseTree.SearchIntersect(w.lastRelativePress.ToSpace().Bounds()) + sort.Slice(pressHits, func(i, j int) bool { + return pressHits[i].Location.Min.Z() < pressHits[i].Location.Max.Z() + }) + for _, sp1 := range pressHits { + for _, sp2 := range hits { + if sp1.CID == sp2.CID { + <-event.TriggerForCallerOn(w.eventHandler, sp1.CID, mouse.RelativeClickOn, &me) + if me.StopPropagation { + return } } } @@ -345,10 +342,10 @@ func (w *Window) EventHandler() event.Handler { } // MostRecentInput returns the most recent input type (e.g keyboard/mouse or joystick) -// recognized by the window. This value will only change if the controller's Config is +// recognized by the window. This value will only change if the window is // set to TrackInputChanges func (w *Window) MostRecentInput() InputType { - return w.mostRecentInput + return InputType(w.mostRecentInput) } func (w *Window) exitWithError(err error) { From 1279c492e9a6a2a08b84ecbaae324613f9fec5a1 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 19 Mar 2022 09:29:13 -0500 Subject: [PATCH 03/41] oak: go mod tidy --- go.mod | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index bef1053c..4ed92d18 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 github.com/disintegration/gift v1.2.0 - github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d // indirect github.com/eaburns/flac v0.0.0-20171003200620-9a6fb92396d1 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 @@ -21,3 +20,8 @@ require ( golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220111092808-5a964db01320 ) + +require ( + github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d // indirect + golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect +) From 94f50c91a353f5cde7c221fd3ffe7309377d1d4e Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 19 Mar 2022 10:12:20 -0500 Subject: [PATCH 04/41] oak: rename CID to CallerID globally, fix render, collision for new events --- audio/pcm/writer_js.go | 142 ++++++++++++++++++ collision/attachSpace.go | 27 ++-- collision/attachSpace_test.go | 2 +- collision/filter.go | 2 +- collision/onCollision.go | 17 +-- collision/onCollision_test.go | 6 +- collision/ray/castFilter.go | 4 +- collision/ray/castLimit.go | 2 +- collision/space.go | 8 +- debugstream/scopeHelper.go | 17 ++- debugtools/inputviz/joystick.go | 8 +- debugtools/inputviz/keyboard.go | 8 +- debugtools/inputviz/mouse.go | 14 +- debugtools/mouse.go | 3 +- entities/doodad.go | 8 +- entities/interactive.go | 4 +- entities/moving.go | 4 +- entities/reactive.go | 4 +- entities/solid.go | 4 +- entities/x/btn/box.go | 6 +- entities/x/btn/button.go | 12 +- entities/x/btn/option.go | 2 +- entities/x/btn/textBox.go | 4 +- entities/x/force/directionSpace.go | 2 +- entities/x/stat/stats.go | 4 +- event/events.go | 4 +- examples/bezier/main.go | 2 +- examples/click-propagation/main.go | 10 +- examples/clipboard/main.go | 6 +- examples/collision-demo/main.go | 8 +- examples/custom-cursor/main.go | 2 +- examples/flappy-bird/main.go | 12 +- examples/joystick-viz/main.go | 2 +- examples/multi-window/main.go | 4 +- examples/particle-demo/main.go | 2 +- examples/piano/main.go | 24 +-- .../platformer-tutorial/2-moving/moving.go | 2 +- .../platformer-tutorial/3-falling/falling.go | 2 +- .../platformer-tutorial/4-jumping/jumping.go | 2 +- .../5-correct-jumping/correct-jumping.go | 2 +- .../6-complete/complete.go | 2 +- examples/pong/main.go | 6 +- examples/radar-demo/main.go | 6 +- examples/rooms/main.go | 2 +- examples/screenopts/main.go | 4 +- examples/slide/show/slide.go | 2 +- examples/slide/show/static/basicSlide.go | 8 +- examples/sprite-demo/main.go | 6 +- .../1-start/start.go | 2 +- .../2-shooting/shooting.go | 4 +- .../3-enemies/enemies.go | 10 +- .../4-sprites/sprites.go | 10 +- .../5-viewport/viewport.go | 10 +- .../6-performance/performance.go | 10 +- examples/zooming/main.go | 2 +- inputTracker_test.go | 2 +- mouse/onCollision_test.go | 6 +- render/interfaceFeatures.go | 2 +- render/logicfps.go | 18 ++- render/particle/allocator.go | 17 ++- render/particle/allocator_test.go | 2 +- render/particle/collisonGenerator.go | 2 +- render/particle/source.go | 32 ++-- render/reverting.go | 2 +- render/sequence.go | 13 +- render/sequence_test.go | 4 +- render/switch.go | 2 +- window.go | 4 + window/window.go | 1 + window_test.go | 10 +- 70 files changed, 375 insertions(+), 224 deletions(-) create mode 100644 audio/pcm/writer_js.go diff --git a/audio/pcm/writer_js.go b/audio/pcm/writer_js.go new file mode 100644 index 00000000..f6c0974e --- /dev/null +++ b/audio/pcm/writer_js.go @@ -0,0 +1,142 @@ +//go:build js + +package pcm + +import ( + "fmt" + "math" + "sync" + "sync/atomic" + "syscall/js" + + "github.com/oakmound/oak/v3/oakerr" +) + +func initOS() error { + return nil +} + +var processorIndex int32 + +func newWriter(f Format) (Writer, error) { + if f.Bits != 32 { + return nil, oakerr.InvalidInput{ + InputName: "f.Bits", + } + } + window := js.Global() + actxConstruct := window.Get("AudioContext") + if actxConstruct.IsUndefined() || actxConstruct.IsNull() { + actxConstruct = window.Get("webkitAudioContext") + } + audioCtx := actxConstruct.New(map[string]interface{}{ + "latencyHint": "interactive", + "sampleRate": f.SampleRate, + }) + + processorName := "oakPCM" + strconv.Itoa(int(atomic.Add(&processorIndex, 1))) + window.Call("registerProcessor", processorName, "js class?") + audioCtx.Get("audioWorklet").Call("addModule", processorName) + + audioBuffer := audioCtx.Call("createBuffer", f.Channels, f.SampleRate*WriterBufferLengthInSeconds, f.SampleRate) + source := audioCtx.Call("createBufferSource") + + channelData := make([]js.Value, f.Channels) + for i := 0; i < int(f.Channels); i++ { + channelData[i] = audioBuffer.Call("getChannelData", i) + + } + + return &jsWriter{ + Format: f, + bufferSize: f.BytesPerSecond() * WriterBufferLengthInSeconds, + audioCtx: audioCtx, + buffer: audioBuffer, + channelData: channelData, + source: source, + }, nil +} + +type jsWriter struct { + sync.Mutex + Format + buffer js.Value + channelData []js.Value // Float32Array + source js.Value + audioCtx js.Value + lockedOffset uint32 + bufferSize uint32 + writeChannel int + writeOffset int + playing bool +} + +func (jsw *jsWriter) Close() error { + jsw.Lock() + defer jsw.Unlock() + + // we can't release this object? + if jsw.playing { + jsw.source.Call("stop") + } + return nil +} + +func (jsw *jsWriter) Reset() error { + jsw.Lock() + defer jsw.Unlock() + // emptyBuff := make([]byte, jsw.bufferSize) + // a, b, err := jsw.buff.LockBytes(0, jsw.bufferSize, 0) + // if err != nil { + // return err + // } + // copy(a, emptyBuff) + // if len(b) != 0 { + // copy(b, emptyBuff) + // } + // err = jsw.buff.UnlockBytes(a, b) + // jsw.Seek(0, io.SeekStart) + + //jsw.audioBuffer.Call("copyToChannel") + // make it a []float32 array somehow // then a byte array? or just convert + // from byte to float32 adaptively + return nil +} + +func (jsw *jsWriter) WritePCM(data []byte) (n int, err error) { + jsw.Lock() + defer jsw.Unlock() + + // we cannot write less than four bytes -- float32 + readAt := 0 + for len(data[readAt:]) >= 4 { + u32 := uint32(data[readAt]) + + uint32(data[readAt+1])<<8 + + uint32(data[readAt+2])<<16 + + uint32(data[readAt+3])<<24 + f32 := float32(u32) / float32(math.MaxInt32) + + jsw.channelData[jsw.writeChannel].SetIndex(jsw.writeOffset, f32) + + readAt += 4 + jsw.writeChannel++ + jsw.writeChannel %= int(jsw.Channels) + if jsw.writeChannel == 0 { + jsw.writeOffset++ + if jsw.writeOffset >= int((jsw.bufferSize/4)/uint32(jsw.Channels)) { + jsw.writeOffset = 0 + } + } + } + + jsw.source.Set("buffer", jsw.buffer) + if !jsw.playing { + fmt.Println("start playing") + jsw.playing = true + jsw.source.Set("loop", true) + jsw.source.Call("connect", jsw.audioCtx.Get("destination")) + jsw.source.Call("start") + } + + return readAt, nil +} diff --git a/collision/attachSpace.go b/collision/attachSpace.go index 7cf1612d..cf006bdf 100644 --- a/collision/attachSpace.go +++ b/collision/attachSpace.go @@ -19,6 +19,7 @@ type AttachSpace struct { aSpace **Space tree *Tree offX, offY float64 + binding event.Binding } func (as *AttachSpace) getAttachSpace() *AttachSpace { @@ -31,7 +32,8 @@ type attachSpace interface { // Attach attaches v to the given space with optional x,y offsets. See AttachSpace. func Attach(v physics.Vector, s *Space, tree *Tree, offsets ...float64) error { - if t, ok := s.CID.E().(attachSpace); ok { + en := event.DefaultCallerMap.GetEntity(s.CID) + if t, ok := en.(attachSpace); ok { as := t.getAttachSpace() as.aSpace = &s as.follow = v @@ -39,7 +41,7 @@ func Attach(v physics.Vector, s *Space, tree *Tree, offsets ...float64) error { if as.tree == nil { as.tree = DefaultTree } - s.CID.Bind(event.Enter, attachSpaceEnter) + as.binding = event.Bind(event.DefaultBus, event.Enter, s.CID, attachSpaceEnter) if len(offsets) > 0 { as.offX = offsets[0] if len(offsets) > 1 { @@ -54,30 +56,21 @@ func Attach(v physics.Vector, s *Space, tree *Tree, offsets ...float64) error { // Detach removes the attachSpaceEnter binding from an entity composed with // AttachSpace func Detach(s *Space) error { - en := s.CID.E() - if _, ok := en.(attachSpace); ok { - event.UnbindBindable( - event.UnbindOption{ - Event: event.Event{ - Name: event.Enter, - CallerID: s.CID, - }, - Fn: attachSpaceEnter, - }, - ) + en := event.DefaultCallerMap.GetEntity(s.CID) + if as, ok := en.(attachSpace); ok { + as.getAttachSpace().binding.Unbind() return nil } return errors.New("this space's entity is not composed of AttachSpace") } -func attachSpaceEnter(id event.CID, _ interface{}) int { - as := id.E().(attachSpace).getAttachSpace() +func attachSpaceEnter(id event.CallerID, _ event.EnterPayload) event.Response { + asIface := event.DefaultCallerMap.GetEntity(id) + as := asIface.(attachSpace).getAttachSpace() x, y := as.follow.X()+as.offX, as.follow.Y()+as.offY if x != (*as.aSpace).X() || y != (*as.aSpace).Y() { - // If this was a nil pointer it would have already crashed but as of release 2.2.0 - // this could error from the space to delete not existing in the rtree. as.tree.UpdateSpace(x, y, (*as.aSpace).GetW(), (*as.aSpace).GetH(), *as.aSpace) } return 0 diff --git a/collision/attachSpace_test.go b/collision/attachSpace_test.go index 99d1f613..2c81e019 100644 --- a/collision/attachSpace_test.go +++ b/collision/attachSpace_test.go @@ -12,7 +12,7 @@ type aspace struct { AttachSpace } -func (as *aspace) Init() event.CID { +func (as *aspace) Init() event.CallerID { return event.NextID(as) } diff --git a/collision/filter.go b/collision/filter.go index e7a07c3c..d0be173e 100644 --- a/collision/filter.go +++ b/collision/filter.go @@ -43,7 +43,7 @@ func Without(tossFn func(*Space) bool) Filter { } // WithoutCIDs will return no spaces with a CID in the input -func WithoutCIDs(cids ...event.CID) Filter { +func WithoutCIDs(cids ...event.CallerID) Filter { return Without(func(s *Space) bool { for _, c := range cids { if s.CID == c { diff --git a/collision/onCollision.go b/collision/onCollision.go index 784aa90c..760f9996 100644 --- a/collision/onCollision.go +++ b/collision/onCollision.go @@ -46,21 +46,20 @@ func PhaseCollisionWithBus(s *Space, tree *Tree, bus event.Handler, entities *ev if oc.tree == nil { oc.tree = DefaultTree } - bus.Bind(event.Enter, s.CID, phaseCollisionEnter(entities)) + event.Bind(bus, event.Enter, s.CID, phaseCollisionEnter(entities)) return nil } return errors.New("This space's entity does not implement collisionPhase") } // CollisionStart/Stop: when a PhaseCollision entity starts/stops touching some label. -// Payload: (Label) the label the entity has started/stopped touching -const ( - Start = "CollisionStart" - Stop = "CollisionStop" +var ( + Start = event.RegisterEvent[Label]() + Stop = event.RegisterEvent[Label]() ) -func phaseCollisionEnter(entities *event.CallerMap) func(id event.CID, nothing interface{}) int { - return func(id event.CID, nothing interface{}) int { +func phaseCollisionEnter(entities *event.CallerMap) func(id event.CallerID, nothing event.EnterPayload) event.Response { + return func(id event.CallerID, _ event.EnterPayload) event.Response { e := entities.GetEntity(id).(collisionPhase) oc := e.getCollisionPhase() @@ -72,7 +71,7 @@ func phaseCollisionEnter(entities *event.CallerMap) func(id event.CID, nothing i for _, h := range hits { l := h.Label if _, ok := oc.Touching[l]; !ok { - id.TriggerBus(Start, l, oc.bus) + event.TriggerForCallerOn(oc.bus, id, Start, l) } newTouching[l] = true } @@ -80,7 +79,7 @@ func phaseCollisionEnter(entities *event.CallerMap) func(id event.CID, nothing i // if we lost any, trigger off collision for l := range oc.Touching { if _, ok := newTouching[l]; !ok { - id.TriggerBus(Stop, l, oc.bus) + event.TriggerForCallerOn(oc.bus, id, Stop, l) } } diff --git a/collision/onCollision_test.go b/collision/onCollision_test.go index 82777fbc..9ca0ee2e 100644 --- a/collision/onCollision_test.go +++ b/collision/onCollision_test.go @@ -12,7 +12,7 @@ type cphase struct { callers *event.CallerMap } -func (cp *cphase) Init() event.CID { +func (cp *cphase) Init() event.CallerID { return cp.callers.NextID(cp) } @@ -37,11 +37,11 @@ func TestCollisionPhase(t *testing.T) { t.Fatalf("phase collision failed: %v", err) } var active bool - bus.Bind("CollisionStart", cid, func(event.CID, interface{}) int { + bus.Bind("CollisionStart", cid, func(event.CallerID, interface{}) int { active = true return 0 }) - bus.Bind("CollisionStop", cid, func(event.CID, interface{}) int { + bus.Bind("CollisionStop", cid, func(event.CallerID, interface{}) int { active = false return 0 }) diff --git a/collision/ray/castFilter.go b/collision/ray/castFilter.go index 06103520..71ba7d7b 100644 --- a/collision/ray/castFilter.go +++ b/collision/ray/castFilter.go @@ -46,7 +46,7 @@ func IgnoreLabels(ls ...collision.Label) CastOption { } // AcceptIDs is equivalent to AcceptLabels, but for CIDs. -func AcceptIDs(ids ...event.CID) CastOption { +func AcceptIDs(ids ...event.CallerID) CastOption { return AddFilter(func(s *collision.Space) bool { for _, id := range ids { if s.CID == id { @@ -58,7 +58,7 @@ func AcceptIDs(ids ...event.CID) CastOption { } // IgnoreIDs is equivalent to IgnoreLabels, but for CIDs. -func IgnoreIDs(ids ...event.CID) CastOption { +func IgnoreIDs(ids ...event.CallerID) CastOption { return AddFilter(func(s *collision.Space) bool { for _, id := range ids { if s.CID == id { diff --git a/collision/ray/castLimit.go b/collision/ray/castLimit.go index 5212958d..46bc245d 100644 --- a/collision/ray/castLimit.go +++ b/collision/ray/castLimit.go @@ -47,7 +47,7 @@ func StopAtLabel(ls ...collision.Label) CastOption { // StopAtID will cause a caster to cease casting as soon as it // hits one of the input CIDs. -func StopAtID(ids ...event.CID) CastOption { +func StopAtID(ids ...event.CallerID) CastOption { return AddLimit(func(ps []collision.Point) bool { z := ps[len(ps)-1].Zone for _, id := range ids { diff --git a/collision/space.go b/collision/space.go index 6d54da3f..c217ac48 100644 --- a/collision/space.go +++ b/collision/space.go @@ -197,7 +197,7 @@ func NewUnassignedSpace(x, y, w, h float64) *Space { } // NewSpace returns a space with an associated caller id -func NewSpace(x, y, w, h float64, cID event.CID) *Space { +func NewSpace(x, y, w, h float64, cID event.CallerID) *Space { return NewFullSpace(x, y, w, h, NilLabel, cID) } @@ -212,7 +212,7 @@ func NewLabeledSpace(x, y, w, h float64, l Label) *Space { } // NewFullSpace returns a space with both a label and a caller id -func NewFullSpace(x, y, w, h float64, l Label, cID event.CID) *Space { +func NewFullSpace(x, y, w, h float64, l Label, cID event.CallerID) *Space { rect := NewRect(x, y, w, h) return &Space{ rect, @@ -223,12 +223,12 @@ func NewFullSpace(x, y, w, h float64, l Label, cID event.CID) *Space { } // NewRect2Space returns a space with an associated caller id from a rect2 -func NewRect2Space(rect floatgeom.Rect2, cID event.CID) *Space { +func NewRect2Space(rect floatgeom.Rect2, cID event.CallerID) *Space { return NewSpace(rect.Min.X(), rect.Min.Y(), rect.W(), rect.H(), cID) } // NewRectSpace creates a colliison space with the specified 3D rectangle -func NewRectSpace(rect floatgeom.Rect3, l Label, cID event.CID) *Space { +func NewRectSpace(rect floatgeom.Rect3, l Label, cID event.CallerID) *Space { return &Space{ rect, l, diff --git a/debugstream/scopeHelper.go b/debugstream/scopeHelper.go index b3e4183e..0d1b176b 100644 --- a/debugstream/scopeHelper.go +++ b/debugstream/scopeHelper.go @@ -69,14 +69,13 @@ const explainMouseDetails = "the mext mouse click on the given window will print func mouseCommands(w window.Window) func([]string) string { return func(tokenString []string) string { - w.EventHandler().GlobalBind("MouseRelease", mouseDetails(w)) + event.Bind(w.EventHandler(), mouse.Release, event.Global, mouseDetails(w)) return "" } } -func mouseDetails(w window.Window) func(event.CID, interface{}) int { - return func(nothing event.CID, mevent interface{}) int { - me := mevent.(mouse.Event) +func mouseDetails(w window.Window) func(event.CallerID, *mouse.Event) event.Response { + return func(nothing event.CallerID, me *mouse.Event) event.Response { viewPos := w.Viewport() x := int(me.X()) + viewPos[0] y := int(me.Y()) + viewPos[1] @@ -86,17 +85,19 @@ func mouseDetails(w window.Window) func(event.CID, interface{}) int { if len(results) == 0 { results = mouse.Hits(loc) } + cm := w.GetCallerMap() + if len(results) > 0 { - i := int(results[0].CID) - if i > 0 && event.HasEntity(event.CID(i)) { - e := event.GetEntity(event.CID(i)) + i := results[0].CID + if i > 0 && cm.HasEntity(i) { + e := cm.GetEntity(i) fmt.Printf("%+v\n", e) } else { fmt.Println("No entity ", i) } } - return event.UnbindSingle + return event.UnbindThis } } diff --git a/debugtools/inputviz/joystick.go b/debugtools/inputviz/joystick.go index ba61d6a3..9834c873 100644 --- a/debugtools/inputviz/joystick.go +++ b/debugtools/inputviz/joystick.go @@ -47,7 +47,7 @@ type Joystick struct { BaseLayer int ctx *scene.Context - event.CID + event.CallerID joy *joystick.Joystick rs map[string]render.Modifiable lastState *joystick.State @@ -57,7 +57,7 @@ type Joystick struct { cancel func() } -func (j *Joystick) Init() event.CID { +func (j *Joystick) Init() event.CallerID { j.CID = j.ctx.CallerMap.NextID(j) return j.CID } @@ -234,7 +234,7 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l } func (j *Joystick) CheckedIDBind(ev string, f func(*Joystick, uint32)) { - j.Bind(ev, func(id event.CID, jid interface{}) int { + j.Bind(ev, func(id event.CallerID, jid interface{}) int { joy, ok := event.GetEntity(id).(*Joystick) if !ok { return 0 @@ -249,7 +249,7 @@ func (j *Joystick) CheckedIDBind(ev string, f func(*Joystick, uint32)) { } func (j *Joystick) CheckedBind(ev string, f func(*Joystick, *joystick.State)) { - j.Bind(ev, func(id event.CID, state interface{}) int { + j.Bind(ev, func(id event.CallerID, state interface{}) int { joy, ok := event.GetEntity(id).(*Joystick) if !ok { return 0 diff --git a/debugtools/inputviz/keyboard.go b/debugtools/inputviz/keyboard.go index 6f21712e..840cdbb8 100644 --- a/debugtools/inputviz/keyboard.go +++ b/debugtools/inputviz/keyboard.go @@ -148,13 +148,13 @@ type Keyboard struct { RenderCharacters bool Font *render.Font - event.CID + event.CallerID ctx *scene.Context rs map[string]*render.Switch } -func (k *Keyboard) Init() event.CID { +func (k *Keyboard) Init() event.CallerID { k.CID = k.ctx.CallerMap.NextID(k) return k.CID } @@ -220,7 +220,7 @@ func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { } } - k.Bind(key.Down, key.Binding(func(id event.CID, ev key.Event) int { + k.Bind(key.Down, key.Binding(func(id event.CallerID, ev key.Event) int { kb, _ := k.ctx.CallerMap.GetEntity(id).(*Keyboard) btn := ev.Code.String()[4:] if kb.rs[btn] == nil { @@ -229,7 +229,7 @@ func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { kb.rs[btn].Set("pressed") return 0 })) - k.Bind(key.Up, key.Binding(func(id event.CID, ev key.Event) int { + k.Bind(key.Up, key.Binding(func(id event.CallerID, ev key.Event) int { kb, _ := k.ctx.CallerMap.GetEntity(id).(*Keyboard) btn := ev.Code.String()[4:] if kb.rs[btn] == nil { diff --git a/debugtools/inputviz/mouse.go b/debugtools/inputviz/mouse.go index 3356a7a0..12964e6d 100644 --- a/debugtools/inputviz/mouse.go +++ b/debugtools/inputviz/mouse.go @@ -17,7 +17,7 @@ type Mouse struct { Rect floatgeom.Rect2 BaseLayer int - event.CID + event.CallerID ctx *scene.Context rs map[mouse.Button]*render.Switch @@ -29,7 +29,7 @@ type Mouse struct { stateInc map[mouse.Button]int } -func (m *Mouse) Init() event.CID { +func (m *Mouse) Init() event.CallerID { m.CID = m.ctx.CallerMap.NextID(m) return m.CID } @@ -98,7 +98,7 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { ctx.DrawStack.Draw(m.posText, m.BaseLayer, layer+2) } - m.Bind(mouse.Press, mouse.Binding(func(id event.CID, ev *mouse.Event) int { + m.Bind(mouse.Press, mouse.Binding(func(id event.CallerID, ev *mouse.Event) int { m, _ := m.ctx.CallerMap.GetEntity(id).(*Mouse) m.rs[ev.Button].Set("pressed") m.stateIncLock.Lock() @@ -106,7 +106,7 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { m.stateIncLock.Unlock() return 0 })) - m.Bind(mouse.Release, mouse.Binding(func(id event.CID, ev *mouse.Event) int { + m.Bind(mouse.Release, mouse.Binding(func(id event.CallerID, ev *mouse.Event) int { m, _ := m.ctx.CallerMap.GetEntity(id).(*Mouse) m.rs[ev.Button].Set("released") m.stateIncLock.Lock() @@ -114,7 +114,7 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { m.stateIncLock.Unlock() return 0 })) - m.Bind(mouse.ScrollDown, mouse.Binding(func(id event.CID, e *mouse.Event) int { + m.Bind(mouse.ScrollDown, mouse.Binding(func(id event.CallerID, e *mouse.Event) int { m, _ := m.ctx.CallerMap.GetEntity(id).(*Mouse) m.rs[mouse.ButtonMiddle].Set("scrolldown") m.stateIncLock.Lock() @@ -130,7 +130,7 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { }) return 0 })) - m.Bind(mouse.ScrollUp, mouse.Binding(func(id event.CID, e *mouse.Event) int { + m.Bind(mouse.ScrollUp, mouse.Binding(func(id event.CallerID, e *mouse.Event) int { m, _ := m.ctx.CallerMap.GetEntity(id).(*Mouse) m.rs[mouse.ButtonMiddle].Set("scrollup") m.stateIncLock.Lock() @@ -146,7 +146,7 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { }) return 0 })) - m.Bind(mouse.Drag, mouse.Binding(func(id event.CID, e *mouse.Event) int { + m.Bind(mouse.Drag, mouse.Binding(func(id event.CallerID, e *mouse.Event) int { m, _ := m.ctx.CallerMap.GetEntity(id).(*Mouse) m.lastMousePos.Point2 = e.Point2 return 0 diff --git a/debugtools/mouse.go b/debugtools/mouse.go index 8aebcac2..348ec851 100644 --- a/debugtools/mouse.go +++ b/debugtools/mouse.go @@ -10,8 +10,7 @@ import ( // DebugMouseRelease will print the position and button pressed of the mouse when the mouse is released, if the given // key is held down at the time. If no key is given, it will always be printed func DebugMouseRelease(ctx *scene.Context, k string) { - ctx.EventHandler.GlobalBind(mouse.Release, func(_ event.CID, ev interface{}) int { - mev, _ := ev.(*mouse.Event) + event.Bind(ctx.EventHandler, mouse.Release, event.Global, func(_ event.CallerID, mev *mouse.Event) event.Response { if k == "" || ctx.KeyState.IsDown(k) { dlog.Info(mev) } diff --git a/entities/doodad.go b/entities/doodad.go index 47c4beff..55b2bb51 100644 --- a/entities/doodad.go +++ b/entities/doodad.go @@ -8,7 +8,7 @@ import ( // A Doodad is an entity composed of a position, a renderable, and a CallerID. type Doodad struct { Point - event.CID + event.CallerID R render.Renderable } @@ -17,7 +17,7 @@ type Doodad struct { // any other CID will assume that the struct containing this doodad has // already been initialized to the passed in CID. // This applies to ALL NewX functions in entities which take in a CID. -func NewDoodad(x, y float64, r render.Renderable, cid event.CID) *Doodad { +func NewDoodad(x, y float64, r render.Renderable, cid event.CallerID) *Doodad { if r != nil { r.SetPos(x, y) } @@ -29,14 +29,14 @@ func NewDoodad(x, y float64, r render.Renderable, cid event.CID) *Doodad { } // Init satisfies event.Entity -func (d *Doodad) Init() event.CID { +func (d *Doodad) Init() event.CallerID { d.CID = event.NextID(d) return d.CID } // GetID returns this Doodad's CID // Consider: are these getters needed? -func (d *Doodad) GetID() event.CID { +func (d *Doodad) GetID() event.CallerID { return d.CID } diff --git a/entities/interactive.go b/entities/interactive.go index ffee9d0b..12f3a450 100644 --- a/entities/interactive.go +++ b/entities/interactive.go @@ -15,7 +15,7 @@ type Interactive struct { // NewInteractive returns a new Interactive func NewInteractive(x, y, w, h float64, r render.Renderable, tree *collision.Tree, - cid event.CID, friction float64) *Interactive { + cid event.CallerID, friction float64) *Interactive { i := Interactive{} cid = cid.Parse(&i) @@ -29,7 +29,7 @@ func NewInteractive(x, y, w, h float64, r render.Renderable, tree *collision.Tre } // Init satisfies event.Entity -func (iv *Interactive) Init() event.CID { +func (iv *Interactive) Init() event.CallerID { cID := event.NextID(iv) iv.CID = cID return cID diff --git a/entities/moving.go b/entities/moving.go index da37e3af..fe4968a0 100644 --- a/entities/moving.go +++ b/entities/moving.go @@ -14,7 +14,7 @@ type Moving struct { } // NewMoving returns a new Moving -func NewMoving(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid event.CID, friction float64) *Moving { +func NewMoving(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid event.CallerID, friction float64) *Moving { m := Moving{} cid = cid.Parse(&m) m.Solid = *NewSolid(x, y, w, h, r, tree, cid) @@ -27,7 +27,7 @@ func NewMoving(x, y, w, h float64, r render.Renderable, tree *collision.Tree, ci } // Init satisfies event.Entity -func (m *Moving) Init() event.CID { +func (m *Moving) Init() event.CallerID { m.CID = event.NextID(m) return m.CID } diff --git a/entities/reactive.go b/entities/reactive.go index 4acfa644..8cea369f 100644 --- a/entities/reactive.go +++ b/entities/reactive.go @@ -17,7 +17,7 @@ type Reactive struct { // NewReactive returns a new Reactive struct. The added space will // be added to the input tree, or DefTree if none is given. -func NewReactive(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid event.CID) *Reactive { +func NewReactive(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid event.CallerID) *Reactive { rct := Reactive{} cid = cid.Parse(&rct) rct.Doodad = *NewDoodad(x, y, r, cid) @@ -73,7 +73,7 @@ func (r *Reactive) GetReactiveSpace() *collision.ReactiveSpace { // Overwrites // Init satisfies event.Entity -func (r *Reactive) Init() event.CID { +func (r *Reactive) Init() event.CallerID { r.CID = event.NextID(r) return r.CID } diff --git a/entities/solid.go b/entities/solid.go index 01b7e0d2..ee104334 100644 --- a/entities/solid.go +++ b/entities/solid.go @@ -17,7 +17,7 @@ type Solid struct { // NewSolid returns an initialized Solid that is not drawn and whose space // belongs to the given collision tree. If nil is given as the tree, it will // belong to collision.DefTree -func NewSolid(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid event.CID) *Solid { +func NewSolid(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid event.CallerID) *Solid { s := Solid{} cid = cid.Parse(&s) s.Doodad = *NewDoodad(x, y, r, cid) @@ -99,7 +99,7 @@ func (s *Solid) HitLabel(classtype collision.Label) *collision.Space { // Overwrites // Init satisfies event.Entity -func (s *Solid) Init() event.CID { +func (s *Solid) Init() event.CallerID { s.CID = event.NextID(s) return s.CID } diff --git a/entities/x/btn/box.go b/entities/x/btn/box.go index 54b3be7b..0ddab483 100644 --- a/entities/x/btn/box.go +++ b/entities/x/btn/box.go @@ -15,7 +15,7 @@ type Box struct { } // NewBox creates a new Box -func NewBox(cid event.CID, x, y, w, h float64, r render.Renderable, layers ...int) *Box { +func NewBox(cid event.CallerID, x, y, w, h float64, r render.Renderable, layers ...int) *Box { b := Box{} cid = cid.Parse(&b) b.Solid = *entities.NewSolid(x, y, w, h, r, mouse.DefaultTree, cid) @@ -27,7 +27,7 @@ func NewBox(cid event.CID, x, y, w, h float64, r render.Renderable, layers ...in } // Init intializes the Box -func (b *Box) Init() event.CID { +func (b *Box) Init() event.CallerID { b.CID = event.NextID(b) return b.CID } @@ -57,4 +57,4 @@ func (b *Box) Destroy() { b.UnbindAll() b.R.Undraw() mouse.Remove(b.GetSpace()) -} \ No newline at end of file +} diff --git a/entities/x/btn/button.go b/entities/x/btn/button.go index 132de378..9fbffb8a 100644 --- a/entities/x/btn/button.go +++ b/entities/x/btn/button.go @@ -28,7 +28,7 @@ type Generator struct { R1 render.Modifiable R2 render.Modifiable RS []render.Modifiable - Cid event.CID + Cid event.CallerID Font *render.Font Layers []int Text string @@ -184,7 +184,7 @@ func (g Generator) generate(parent *Generator) Btn { filteredK := "Filtered" + k g.Bindings[filteredK] = g.Bindings[k] g.Bindings[k] = []event.Bindable{ - func(id event.CID, button interface{}) int { + func(id event.CallerID, button interface{}) int { btn := id.E().(Btn) mEvent, ok := button.(*mouse.Event) // If the passed event is not a mouse event dont filter on location. @@ -239,8 +239,8 @@ type switcher interface { } // toggleFxn sets up the mouseclick binding for toggle buttons created for goreport cyclo decrease -func toggleFxn(g Generator) func(id event.CID, nothing interface{}) int { - return func(id event.CID, nothing interface{}) int { +func toggleFxn(g Generator) func(id event.CallerID, nothing interface{}) int { + return func(id event.CallerID, nothing interface{}) int { btn := event.GetEntity(id).(Btn) if btn.GetRenderable().(switcher).Get() == "on" { if g.Group != nil && g.Group.active == btn { @@ -267,8 +267,8 @@ func toggleFxn(g Generator) func(id event.CID, nothing interface{}) int { } // listFxn sets up the mouseclick binding for list buttons created for goreport cyclo reduction -func listFxn(g Generator) func(id event.CID, button interface{}) int { - return func(id event.CID, button interface{}) int { +func listFxn(g Generator) func(id event.CallerID, button interface{}) int { + return func(id event.CallerID, button interface{}) int { btn := event.GetEntity(id).(Btn) i := *g.ListChoice mEvent := button.(*mouse.Event) diff --git a/entities/x/btn/option.go b/entities/x/btn/option.go index b5b1b3cd..fbb4fbcd 100644 --- a/entities/x/btn/option.go +++ b/entities/x/btn/option.go @@ -64,7 +64,7 @@ func Offset(x, y float64) Option { } //CID sets the starting CID of the button to be generated -func CID(c event.CID) Option { +func CID(c event.CallerID) Option { return func(g Generator) Generator { g.Cid = c return g diff --git a/entities/x/btn/textBox.go b/entities/x/btn/textBox.go index cbe4f442..071637db 100644 --- a/entities/x/btn/textBox.go +++ b/entities/x/btn/textBox.go @@ -14,13 +14,13 @@ type TextBox struct { } // Init creates the CID -func (b *TextBox) Init() event.CID { +func (b *TextBox) Init() event.CallerID { b.CID = event.NextID(b) return b.CID } // NewTextBox creates a textbox -func NewTextBox(cid event.CID, x, y, w, h, txtX, txtY float64, +func NewTextBox(cid event.CallerID, x, y, w, h, txtX, txtY float64, f *render.Font, r render.Renderable, layers ...int) *TextBox { if f == nil { diff --git a/entities/x/force/directionSpace.go b/entities/x/force/directionSpace.go index 83014cee..b7d4cc8e 100644 --- a/entities/x/force/directionSpace.go +++ b/entities/x/force/directionSpace.go @@ -13,7 +13,7 @@ type DirectionSpace struct { } // Init initializes the DirectionSpace as an entity -func (ds *DirectionSpace) Init() event.CID { +func (ds *DirectionSpace) Init() event.CallerID { return event.NextID(ds) } diff --git a/entities/x/stat/stats.go b/entities/x/stat/stats.go index f7fe8358..d0c09a03 100644 --- a/entities/x/stat/stats.go +++ b/entities/x/stat/stats.go @@ -23,7 +23,7 @@ func TimedOff(eventName string) event.Bindable { // TimedBind returns a binding that will trigger toggling on or off the given event func TimedBind(eventName string, on bool) event.Bindable { - return func(event.CID, interface{}) int { + return func(event.CallerID, interface{}) int { event.Trigger(eventName, timedStat{eventName, on}) return 0 } @@ -31,7 +31,7 @@ func TimedBind(eventName string, on bool) event.Bindable { // Bind returns a binding that will increment the given event by 'inc' func Bind(eventName string, inc int) event.Bindable { - return func(event.CID, interface{}) int { + return func(event.CallerID, interface{}) int { event.Trigger(eventName, stat{eventName, inc}) return 0 } diff --git a/event/events.go b/event/events.go index 45b5a886..83515072 100644 --- a/event/events.go +++ b/event/events.go @@ -26,7 +26,7 @@ func RegisterEvent[T any]() EventID[T] { } } -type NoPayload struct{} +type NoPayload = struct{} // EnterPayload is the payload sent down to Enter bindings type EnterPayload struct { @@ -38,8 +38,6 @@ type EnterPayload struct { var ( // Enter: the beginning of every logical frame. Enter = RegisterEvent[EnterPayload]() - // AnimationEnd: Triggered on animations CIDs when they loop from the last to the first frame - AnimationEnd = RegisterEvent[NoPayload]() // ViewportUpdate: Triggered when the position of of the viewport changes ViewportUpdate = RegisterEvent[intgeom.Point2]() // OnStop: Triggered when the engine is stopped. diff --git a/examples/bezier/main.go b/examples/bezier/main.go index f7b3b18a..fd78d848 100644 --- a/examples/bezier/main.go +++ b/examples/bezier/main.go @@ -55,7 +55,7 @@ func main() { oak.AddScene("bezier", scene.Scene{Start: func(*scene.Context) { mouseFloats := []float64{} - event.GlobalBind(mouse.Press, func(_ event.CID, mouseEvent interface{}) int { + event.GlobalBind(mouse.Press, func(_ event.CallerID, mouseEvent interface{}) int { me := mouseEvent.(*mouse.Event) // Left click to add a point to the curve if me.Button == mouse.ButtonLeft { diff --git a/examples/click-propagation/main.go b/examples/click-propagation/main.go index c56e6fc0..ba3304bc 100644 --- a/examples/click-propagation/main.go +++ b/examples/click-propagation/main.go @@ -34,13 +34,13 @@ func main() { } type hoverButton struct { - id event.CID + id event.CallerID mouse.CollisionPhase *changingColorBox } -func (hb *hoverButton) Init() event.CID { +func (hb *hoverButton) Init() event.CallerID { hb.id = event.NextID(hb) return hb.id } @@ -57,7 +57,7 @@ func newHoverButton(x, y, w, h float64, clr color.RGBA, layer int) { mouse.PhaseCollision(sp) render.Draw(hb.changingColorBox, 0, layer) - hb.id.Bind(mouse.ClickOn, func(c event.CID, i interface{}) int { + hb.id.Bind(mouse.ClickOn, func(c event.CallerID, i interface{}) int { hb := event.GetEntity(c).(*hoverButton) me := i.(*mouse.Event) fmt.Println(c, me.Point2) @@ -65,7 +65,7 @@ func newHoverButton(x, y, w, h float64, clr color.RGBA, layer int) { me.StopPropagation = true return 0 }) - hb.id.Bind(mouse.Start, func(c event.CID, i interface{}) int { + hb.id.Bind(mouse.Start, func(c event.CallerID, i interface{}) int { fmt.Println("start") hb := event.GetEntity(c).(*hoverButton) me := i.(*mouse.Event) @@ -73,7 +73,7 @@ func newHoverButton(x, y, w, h float64, clr color.RGBA, layer int) { me.StopPropagation = true return 0 }) - hb.id.Bind(mouse.Stop, func(c event.CID, i interface{}) int { + hb.id.Bind(mouse.Stop, func(c event.CallerID, i interface{}) int { fmt.Println("stop") hb := event.GetEntity(c).(*hoverButton) me := i.(*mouse.Event) diff --git a/examples/clipboard/main.go b/examples/clipboard/main.go index 76fc8e5c..da8db2f3 100644 --- a/examples/clipboard/main.go +++ b/examples/clipboard/main.go @@ -33,7 +33,7 @@ func newClipboardCopyText(text string, x, y float64) { btn.Pos(x, y), btn.Height(20), btn.FitText(20), - btn.Click(func(event.CID, interface{}) int { + btn.Click(func(event.CallerID, interface{}) int { err := clipboard.WriteAll(text) if err != nil { fmt.Println(err) @@ -52,7 +52,7 @@ func newClipboardPaster(placeholder string, x, y float64) { btn.Pos(x, y), btn.Height(20), btn.FitText(20), - btn.Binding(key.Down+key.V, func(_ event.CID, payload interface{}) int { + btn.Binding(key.Down+key.V, func(_ event.CallerID, payload interface{}) int { kv := payload.(key.Event) if kv.Modifiers&gokey.ModControl == gokey.ModControl { got, err := clipboard.ReadAll() @@ -64,7 +64,7 @@ func newClipboardPaster(placeholder string, x, y float64) { } return 0 }), - btn.Click(func(event.CID, interface{}) int { + btn.Click(func(event.CallerID, interface{}) int { got, err := clipboard.ReadAll() if err != nil { fmt.Println(err) diff --git a/examples/collision-demo/main.go b/examples/collision-demo/main.go index 41210b75..5b5e5843 100644 --- a/examples/collision-demo/main.go +++ b/examples/collision-demo/main.go @@ -26,7 +26,7 @@ func main() { collision.Attach(act.Vector, act.Space, nil, 0, 0) - act.Bind(event.Enter, func(event.CID, interface{}) int { + act.Bind(event.Enter, func(event.CallerID, interface{}) int { if act.ShouldUpdate { act.ShouldUpdate = false act.R.Undraw() @@ -56,7 +56,7 @@ func main() { render.Draw(act.R, 0, 1) collision.PhaseCollision(act.Space, nil) - act.Bind(collision.Start, func(id event.CID, label interface{}) int { + act.Bind(collision.Start, func(id event.CallerID, label interface{}) int { l := label.(collision.Label) switch l { case RED: @@ -75,7 +75,7 @@ func main() { } return 0 }) - act.Bind(collision.Stop, func(id event.CID, label interface{}) int { + act.Bind(collision.Stop, func(id event.CallerID, label interface{}) int { l := label.(collision.Label) switch l { case RED: @@ -136,7 +136,7 @@ type AttachCollisionTest struct { nextR render.Renderable } -func (act *AttachCollisionTest) Init() event.CID { +func (act *AttachCollisionTest) Init() event.CallerID { return event.NextID(act) } diff --git a/examples/custom-cursor/main.go b/examples/custom-cursor/main.go index 863e0ae7..e46e292d 100644 --- a/examples/custom-cursor/main.go +++ b/examples/custom-cursor/main.go @@ -37,7 +37,7 @@ func main() { ) ctx.DrawStack.Draw(box) - ctx.EventHandler.GlobalBind(mouse.Drag, func(_ event.CID, me interface{}) int { + ctx.EventHandler.GlobalBind(mouse.Drag, func(_ event.CallerID, me interface{}) int { mouseEvent := me.(*mouse.Event) box.SetPos(mouseEvent.X(), mouseEvent.Y()) return 0 diff --git a/examples/flappy-bird/main.go b/examples/flappy-bird/main.go index 6f6adbb3..ec363dfe 100644 --- a/examples/flappy-bird/main.go +++ b/examples/flappy-bird/main.go @@ -70,7 +70,7 @@ type Flappy struct { } // Init satisfies the event.Entity interface -func (f *Flappy) Init() event.CID { +func (f *Flappy) Init() event.CallerID { return event.NextID(f) } @@ -87,7 +87,7 @@ func newFlappy(x, y float64) *Flappy { f.R.SetLayer(1) render.Draw(f.R, 0) - f.Bind(event.Enter, func(event.CID, interface{}) int { + f.Bind(event.Enter, func(event.CallerID, interface{}) int { f.ShiftPos(f.Delta.X(), f.Delta.Y()) f.Add(f.Delta) if f.Delta.Y() > 10 { @@ -109,11 +109,11 @@ func newFlappy(x, y float64) *Flappy { } return 0 }) - f.Bind(mouse.Press, func(event.CID, interface{}) int { + f.Bind(mouse.Press, func(event.CallerID, interface{}) int { f.Delta.ShiftY(-4) return 0 }) - f.Bind(key.Down+key.W, func(event.CID, interface{}) int { + f.Bind(key.Down+key.W, func(event.CallerID, interface{}) int { f.Delta.ShiftY(-4) return 0 }) @@ -127,7 +127,7 @@ type Pillar struct { } // Init satisfies the event.Entity interface -func (p *Pillar) Init() event.CID { +func (p *Pillar) Init() event.CallerID { return event.NextID(p) } @@ -159,7 +159,7 @@ func newPillarPair() { newPillar(641, pos+span, 480-(pos+span), false) } -func enterPillar(id event.CID, nothing interface{}) int { +func enterPillar(id event.CallerID, nothing interface{}) int { p := event.GetEntity(id).(*Pillar) p.ShiftX(-2) if p.X()+p.W < 0 { diff --git a/examples/joystick-viz/main.go b/examples/joystick-viz/main.go index 946da118..a0a1c40a 100644 --- a/examples/joystick-viz/main.go +++ b/examples/joystick-viz/main.go @@ -22,7 +22,7 @@ func main() { *latestInput = "Latest Input: Keyboard+Mouse" ctx.DrawStack.Draw(render.NewStrPtrText(latestInput, 10, 460), 4) ctx.DrawStack.Draw(render.NewText("Space to Vibrate", 10, 440), 4) - ctx.EventHandler.GlobalBind(event.InputChange, func(_ event.CID, payload interface{}) int { + ctx.EventHandler.GlobalBind(event.InputChange, func(_ event.CallerID, payload interface{}) int { input := payload.(oak.InputType) switch input { case oak.InputJoystick: diff --git a/examples/multi-window/main.go b/examples/multi-window/main.go index e07cfa15..28e123f8 100644 --- a/examples/multi-window/main.go +++ b/examples/multi-window/main.go @@ -26,7 +26,7 @@ func main() { ctx.DrawStack.Draw(cb, 0) dFPS := render.NewDrawFPS(0.1, nil, 600, 10) ctx.DrawStack.Draw(dFPS, 1) - ctx.EventHandler.GlobalBind(mouse.Press, mouse.Binding(func(_ event.CID, me *mouse.Event) int { + ctx.EventHandler.GlobalBind(mouse.Press, mouse.Binding(func(_ event.CallerID, me *mouse.Event) int { cb.SetPos(me.X(), me.Y()) return 0 })) @@ -55,7 +55,7 @@ func main() { ctx.DrawStack.Draw(cb, 0) dFPS := render.NewDrawFPS(0.1, nil, 600, 10) ctx.DrawStack.Draw(dFPS, 1) - ctx.EventHandler.GlobalBind(mouse.Press, mouse.Binding(func(_ event.CID, me *mouse.Event) int { + ctx.EventHandler.GlobalBind(mouse.Press, mouse.Binding(func(_ event.CallerID, me *mouse.Event) int { cb.SetPos(me.X(), me.Y()) return 0 })) diff --git a/examples/particle-demo/main.go b/examples/particle-demo/main.go index 7112d4b1..ab91c43c 100644 --- a/examples/particle-demo/main.go +++ b/examples/particle-demo/main.go @@ -56,7 +56,7 @@ func parseShape(args []string) shape.Shape { func main() { debugstream.AddCommand(debugstream.Command{Name: "followMouse", Operation: func(args []string) string { - event.GlobalBind(event.Enter, func(event.CID, interface{}) int { + event.GlobalBind(event.Enter, func(event.CallerID, interface{}) int { // It'd be interesting to attach to the mouse position src.SetPos(float64(mouse.LastEvent.X()), float64(mouse.LastEvent.Y())) return 0 diff --git a/examples/piano/main.go b/examples/piano/main.go index f2bbae6f..b9bd2c1f 100644 --- a/examples/piano/main.go +++ b/examples/piano/main.go @@ -93,7 +93,7 @@ func newKey(note synth.Pitch, c keyColor, k string) *entities.Solid { s.Space.Label = labelWhiteKey } mouse.UpdateSpace(s.X(), s.Y(), s.W, s.H, s.Space) - s.Bind(key.Down+k, func(c event.CID, i interface{}) int { + s.Bind(key.Down+k, func(c event.CallerID, i interface{}) int { if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { return 0 } @@ -101,7 +101,7 @@ func newKey(note synth.Pitch, c keyColor, k string) *entities.Solid { sw.Set("down") return 0 }) - s.Bind(key.Up+k, func(c event.CID, i interface{}) int { + s.Bind(key.Up+k, func(c event.CallerID, i interface{}) int { if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { return 0 } @@ -109,14 +109,14 @@ func newKey(note synth.Pitch, c keyColor, k string) *entities.Solid { sw.Set("up") return 0 }) - s.Bind(mouse.PressOn, func(c event.CID, i interface{}) int { + s.Bind(mouse.PressOn, func(c event.CallerID, i interface{}) int { playPitch(note) me := i.(*mouse.Event) me.StopPropagation = true sw.Set("down") return 0 }) - s.Bind(mouse.Release, func(c event.CID, i interface{}) int { + s.Bind(mouse.Release, func(c event.CallerID, i interface{}) int { releasePitch(note) sw.Set("up") return 0 @@ -223,25 +223,25 @@ func main() { i++ } // Consider: Adding volume control - event.GlobalBind(key.Down+key.S, func(c event.CID, i interface{}) int { + event.GlobalBind(key.Down+key.S, func(c event.CallerID, i interface{}) int { if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { synthKind = src.SinPCM } return 0 }) - event.GlobalBind(key.Down+key.W, func(c event.CID, i interface{}) int { + event.GlobalBind(key.Down+key.W, func(c event.CallerID, i interface{}) int { if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { synthKind = src.SawPCM } return 0 }) - event.GlobalBind(key.Down+key.T, func(c event.CID, i interface{}) int { + event.GlobalBind(key.Down+key.T, func(c event.CallerID, i interface{}) int { if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { synthKind = src.TrianglePCM } return 0 }) - event.GlobalBind(key.Down+key.P, func(c event.CID, i interface{}) int { + event.GlobalBind(key.Down+key.P, func(c event.CallerID, i interface{}) int { if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { synthKind = src.PulsePCM(2) } @@ -252,7 +252,7 @@ func main() { render.Draw(help1) render.Draw(help2) - event.GlobalBind(mouse.ScrollDown, func(c event.CID, i interface{}) int { + event.GlobalBind(mouse.ScrollDown, func(c event.CallerID, i interface{}) int { mag := globalMagnification - 0.05 if mag < 1 { mag = 1 @@ -260,7 +260,7 @@ func main() { globalMagnification = mag return 0 }) - event.GlobalBind(mouse.ScrollUp, func(c event.CID, i interface{}) int { + event.GlobalBind(mouse.ScrollUp, func(c event.CallerID, i interface{}) int { globalMagnification += 0.05 return 0 }) @@ -275,7 +275,7 @@ func main() { } type pcmMonitor struct { - event.CID + event.CallerID render.LayeredPoint pcm.Writer pcm.Format @@ -297,7 +297,7 @@ func newPCMMonitor(w pcm.Writer) *pcmMonitor { return pm } -func (pm *pcmMonitor) Init() event.CID { +func (pm *pcmMonitor) Init() event.CallerID { pm.CID = event.NextID(pm) return pm.CID } diff --git a/examples/platformer-tutorial/2-moving/moving.go b/examples/platformer-tutorial/2-moving/moving.go index f50cfd82..0ab7b1e2 100644 --- a/examples/platformer-tutorial/2-moving/moving.go +++ b/examples/platformer-tutorial/2-moving/moving.go @@ -25,7 +25,7 @@ func main() { char.Speed = physics.NewVector(3, 3) - char.Bind(event.Enter, func(id event.CID, nothing interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { char := event.GetEntity(id).(*entities.Moving) // Move left and right with A and D if oak.IsDown(key.A) { diff --git a/examples/platformer-tutorial/3-falling/falling.go b/examples/platformer-tutorial/3-falling/falling.go index cad71106..9f3c9b66 100644 --- a/examples/platformer-tutorial/3-falling/falling.go +++ b/examples/platformer-tutorial/3-falling/falling.go @@ -36,7 +36,7 @@ func main() { fallSpeed := .1 - char.Bind(event.Enter, func(id event.CID, nothing interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { char := event.GetEntity(id).(*entities.Moving) // Move left and right with A and D if oak.IsDown(key.A) { diff --git a/examples/platformer-tutorial/4-jumping/jumping.go b/examples/platformer-tutorial/4-jumping/jumping.go index ea83252a..1f89019b 100644 --- a/examples/platformer-tutorial/4-jumping/jumping.go +++ b/examples/platformer-tutorial/4-jumping/jumping.go @@ -36,7 +36,7 @@ func main() { fallSpeed := .1 - char.Bind(event.Enter, func(id event.CID, nothing interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { char := event.GetEntity(id).(*entities.Moving) // Move left and right with A and D if oak.IsDown(key.A) { diff --git a/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go b/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go index 6691f3db..60fc9ce1 100644 --- a/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go +++ b/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go @@ -36,7 +36,7 @@ func main() { fallSpeed := .1 - char.Bind(event.Enter, func(id event.CID, nothing interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { char := event.GetEntity(id).(*entities.Moving) // Move left and right with A and D if oak.IsDown(key.A) { diff --git a/examples/platformer-tutorial/6-complete/complete.go b/examples/platformer-tutorial/6-complete/complete.go index 5decabf7..7b481368 100644 --- a/examples/platformer-tutorial/6-complete/complete.go +++ b/examples/platformer-tutorial/6-complete/complete.go @@ -38,7 +38,7 @@ func main() { fallSpeed := .2 - char.Bind(event.Enter, func(id event.CID, nothing interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { char := event.GetEntity(id).(*entities.Moving) // Move left and right with A and D diff --git a/examples/pong/main.go b/examples/pong/main.go index ec8efbeb..7ce8154e 100644 --- a/examples/pong/main.go +++ b/examples/pong/main.go @@ -41,7 +41,7 @@ func main() { func newBall(x, y float64) { b := entities.NewMoving(x, y, 10, 10, render.NewColorBoxR(10, 10, color.RGBA{255, 255, 255, 255}), nil, 0, 0) render.Draw(b.R, 2) - b.Bind(event.Enter, func(id event.CID, nothing interface{}) int { + b.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { if b.Delta.X() == 0 && b.Delta.Y() == 0 { b.Delta.SetY((rand.Float64() - 0.5) * 4) b.Delta.SetX((rand.Float64() - 0.5) * 16) @@ -82,8 +82,8 @@ func newPaddle(x, y float64, player int) { } } -func enterPaddle(up, down string) func(event.CID, interface{}) int { - return func(id event.CID, nothing interface{}) int { +func enterPaddle(up, down string) func(event.CallerID, interface{}) int { + return func(id event.CallerID, nothing interface{}) int { p := id.E().(*entities.Moving) p.Delta.SetY(0) if oak.IsDown(up) { diff --git a/examples/radar-demo/main.go b/examples/radar-demo/main.go index 235fa48b..201a961f 100644 --- a/examples/radar-demo/main.go +++ b/examples/radar-demo/main.go @@ -37,7 +37,7 @@ func main() { oak.SetViewportBounds(intgeom.NewRect2(0, 0, xLimit, yLimit)) moveRect := floatgeom.NewRect2(0, 0, xLimit, yLimit) - char.Bind(event.Enter, func(event.CID, interface{}) int { + char.Bind(event.Enter, func(event.CallerID, interface{}) int { move.WASD(char) move.Limit(char, moveRect) move.CenterScreenOn(char) @@ -87,7 +87,7 @@ type enemyOnRadar struct { *entities.Moving } -func (eor *enemyOnRadar) Init() event.CID { +func (eor *enemyOnRadar) Init() event.CallerID { return event.NextID(eor) } func newEnemyOnRadar(x, y float64) *enemyOnRadar { @@ -98,7 +98,7 @@ func newEnemyOnRadar(x, y float64) *enemyOnRadar { return eor } -func standardEnemyMove(id event.CID, nothing interface{}) int { +func standardEnemyMove(id event.CallerID, nothing interface{}) int { eor := event.GetEntity(id).(*enemyOnRadar) if eor.X() < 0 { eor.Delta.SetPos(math.Abs(eor.Speed.X()), (eor.Speed.Y())) diff --git a/examples/rooms/main.go b/examples/rooms/main.go index 181e3c56..91e02fa5 100644 --- a/examples/rooms/main.go +++ b/examples/rooms/main.go @@ -49,7 +49,7 @@ func main() { var transitioning bool var totalTransitionDelta intgeom.Point2 var transitionDelta intgeom.Point2 - char.Bind(event.Enter, func(event.CID, interface{}) int { + char.Bind(event.Enter, func(event.CallerID, interface{}) int { dir, ok := isOffScreen(ctx, char) if !transitioning && ok { transitioning = true diff --git a/examples/screenopts/main.go b/examples/screenopts/main.go index d1b0b9bd..a0398b03 100644 --- a/examples/screenopts/main.go +++ b/examples/screenopts/main.go @@ -23,7 +23,7 @@ func main() { borderless := borderlessAtStart fullscreen := fullscreenAtStart - event.GlobalBind(key.Down+key.F, func(event.CID, interface{}) int { + event.GlobalBind(key.Down+key.F, func(event.CallerID, interface{}) int { fullscreen = !fullscreen err := oak.SetFullScreen(fullscreen) if err != nil { @@ -32,7 +32,7 @@ func main() { } return 0 }) - event.GlobalBind(key.Down+key.B, func(event.CID, interface{}) int { + event.GlobalBind(key.Down+key.B, func(event.CallerID, interface{}) int { borderless = !borderless err := oak.SetBorderless(borderless) if err != nil { diff --git a/examples/slide/show/slide.go b/examples/slide/show/slide.go index acf4fa7c..90d9af17 100644 --- a/examples/slide/show/slide.go +++ b/examples/slide/show/slide.go @@ -102,7 +102,7 @@ func Start(width, height int, slides ...Slide) { float64(ctx.Window.Height()-50), ), ) - event.GlobalBind("KeyDownSpacebar", func(event.CID, interface{}) int { + event.GlobalBind("KeyDownSpacebar", func(event.CallerID, interface{}) int { reset = true return 0 }) diff --git a/examples/slide/show/static/basicSlide.go b/examples/slide/show/static/basicSlide.go index b0a4e803..29c930ba 100644 --- a/examples/slide/show/static/basicSlide.go +++ b/examples/slide/show/static/basicSlide.go @@ -23,22 +23,22 @@ type Slide struct { func (ss *Slide) Init() { oak.SetFullScreen(true) render.Draw(ss.Rs, 0) - event.GlobalBind("KeyUp"+ss.ContinueKey, func(event.CID, interface{}) int { + event.GlobalBind("KeyUp"+ss.ContinueKey, func(event.CallerID, interface{}) int { fmt.Println("continue key pressed") ss.cont = true return 0 }) - event.GlobalBind("KeyUp"+ss.PrevKey, func(event.CID, interface{}) int { + event.GlobalBind("KeyUp"+ss.PrevKey, func(event.CallerID, interface{}) int { fmt.Println("prev key pressed") ss.prev = true return 0 }) - event.GlobalBind("KeyUpEscape", func(event.CID, interface{}) int { + event.GlobalBind("KeyUpEscape", func(event.CallerID, interface{}) int { os.Exit(0) return 0 }) if ss.OnClick != nil { - event.GlobalBind("MousePress", func(event.CID, interface{}) int { + event.GlobalBind("MousePress", func(event.CallerID, interface{}) int { ss.OnClick() return 0 }) diff --git a/examples/sprite-demo/main.go b/examples/sprite-demo/main.go index a427072e..20d7eb89 100644 --- a/examples/sprite-demo/main.go +++ b/examples/sprite-demo/main.go @@ -37,7 +37,7 @@ func main() { render.Draw(layerTxt, 0) NewGopher(layer) layer++ - event.GlobalBind(event.Enter, func(event.CID, interface{}) int { + event.GlobalBind(event.Enter, func(event.CallerID, interface{}) int { if oak.IsDown("K") { NewGopher(layer) layer++ @@ -77,7 +77,7 @@ type Gopher struct { } // Init sets up a gophers CID -func (g *Gopher) Init() event.CID { +func (g *Gopher) Init() event.CallerID { return event.NextID(g) } @@ -98,7 +98,7 @@ func NewGopher(layer int) { render.Draw(goph.R, 0) } -func gophEnter(cid event.CID, nothing interface{}) int { +func gophEnter(cid event.CallerID, nothing interface{}) int { goph := event.GetEntity(cid).(*Gopher) // Compare against this version of rotation diff --git a/examples/top-down-shooter-tutorial/1-start/start.go b/examples/top-down-shooter-tutorial/1-start/start.go index 8a1d6671..46a7a6ec 100644 --- a/examples/top-down-shooter-tutorial/1-start/start.go +++ b/examples/top-down-shooter-tutorial/1-start/start.go @@ -33,7 +33,7 @@ func main() { char.Speed = physics.NewVector(5, 5) render.Draw(char.R) - char.Bind(event.Enter, func(id event.CID, _ interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { char := event.GetEntity(id).(*entities.Moving) char.Delta.Zero() if oak.IsDown(key.W) { diff --git a/examples/top-down-shooter-tutorial/2-shooting/shooting.go b/examples/top-down-shooter-tutorial/2-shooting/shooting.go index 4d1e4493..9c9d484f 100644 --- a/examples/top-down-shooter-tutorial/2-shooting/shooting.go +++ b/examples/top-down-shooter-tutorial/2-shooting/shooting.go @@ -35,7 +35,7 @@ func main() { char.Speed = physics.NewVector(5, 5) render.Draw(char.R) - char.Bind(event.Enter, func(id event.CID, _ interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { char := event.GetEntity(id).(*entities.Moving) char.Delta.Zero() if oak.IsDown(key.W) { @@ -59,7 +59,7 @@ func main() { return 0 }) - char.Bind(mouse.Press, func(id event.CID, me interface{}) int { + char.Bind(mouse.Press, func(id event.CallerID, me interface{}) int { char := event.GetEntity(id).(*entities.Moving) mevent := me.(*mouse.Event) ctx.DrawForTime( diff --git a/examples/top-down-shooter-tutorial/3-enemies/enemies.go b/examples/top-down-shooter-tutorial/3-enemies/enemies.go index 65ce4577..723c1cb2 100644 --- a/examples/top-down-shooter-tutorial/3-enemies/enemies.go +++ b/examples/top-down-shooter-tutorial/3-enemies/enemies.go @@ -45,7 +45,7 @@ func main() { playerPos = char.Point.Vector render.Draw(char.R) - char.Bind(event.Enter, func(id event.CID, _ interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { char := event.GetEntity(id).(*entities.Moving) char.Delta.Zero() if oak.IsDown(key.W) { @@ -69,7 +69,7 @@ func main() { return 0 }) - char.Bind(mouse.Press, func(id event.CID, me interface{}) int { + char.Bind(mouse.Press, func(id event.CallerID, me interface{}) int { char := event.GetEntity(id).(*entities.Moving) mevent := me.(*mouse.Event) x := char.X() + char.W/2 @@ -86,7 +86,7 @@ func main() { return 0 }) - event.GlobalBind(event.Enter, func(_ event.CID, frames interface{}) int { + event.GlobalBind(event.Enter, func(_ event.CallerID, frames interface{}) int { enterPayload := frames.(event.EnterPayload) if enterPayload.FramesElapsed%EnemyRefresh == 0 { go NewEnemy(ctx) @@ -118,7 +118,7 @@ func NewEnemy(ctx *scene.Context) { enemy.UpdateLabel(Enemy) - enemy.Bind(event.Enter, func(id event.CID, _ interface{}) int { + enemy.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { enemy := event.GetEntity(id).(*entities.Solid) // move towards the player x, y := enemy.GetPos() @@ -129,7 +129,7 @@ func NewEnemy(ctx *scene.Context) { return 0 }) - enemy.Bind("Destroy", func(id event.CID, _ interface{}) int { + enemy.Bind("Destroy", func(id event.CallerID, _ interface{}) int { enemy := event.GetEntity(id).(*entities.Solid) enemy.Destroy() return 0 diff --git a/examples/top-down-shooter-tutorial/4-sprites/sprites.go b/examples/top-down-shooter-tutorial/4-sprites/sprites.go index 26cbd871..0fd5b783 100644 --- a/examples/top-down-shooter-tutorial/4-sprites/sprites.go +++ b/examples/top-down-shooter-tutorial/4-sprites/sprites.go @@ -67,7 +67,7 @@ func main() { playerPos = char.Point.Vector render.Draw(char.R, 2) - char.Bind(event.Enter, func(id event.CID, _ interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { char := event.GetEntity(id).(*entities.Moving) char.Delta.Zero() if oak.IsDown(key.W) { @@ -103,7 +103,7 @@ func main() { return 0 }) - char.Bind(mouse.Press, func(id event.CID, me interface{}) int { + char.Bind(mouse.Press, func(id event.CallerID, me interface{}) int { char := event.GetEntity(id).(*entities.Moving) mevent := me.(*mouse.Event) x := char.X() + char.W/2 @@ -121,7 +121,7 @@ func main() { }) // Create enemies periodically - event.GlobalBind(event.Enter, func(_ event.CID, frames interface{}) int { + event.GlobalBind(event.Enter, func(_ event.CallerID, frames interface{}) int { enterPayload := frames.(event.EnterPayload) if enterPayload.FramesElapsed%EnemyRefresh == 0 { go NewEnemy(ctx) @@ -178,7 +178,7 @@ func NewEnemy(ctx *scene.Context) { enemy.UpdateLabel(Enemy) - enemy.Bind(event.Enter, func(id event.CID, _ interface{}) int { + enemy.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { enemy := event.GetEntity(id).(*entities.Solid) // move towards the player x, y := enemy.GetPos() @@ -201,7 +201,7 @@ func NewEnemy(ctx *scene.Context) { return 0 }) - enemy.Bind("Destroy", func(id event.CID, _ interface{}) int { + enemy.Bind("Destroy", func(id event.CallerID, _ interface{}) int { enemy := event.GetEntity(id).(*entities.Solid) enemy.Destroy() return 0 diff --git a/examples/top-down-shooter-tutorial/5-viewport/viewport.go b/examples/top-down-shooter-tutorial/5-viewport/viewport.go index f3ceab38..5adae2db 100644 --- a/examples/top-down-shooter-tutorial/5-viewport/viewport.go +++ b/examples/top-down-shooter-tutorial/5-viewport/viewport.go @@ -75,7 +75,7 @@ func main() { playerPos = char.Point.Vector render.Draw(char.R, 2) - char.Bind(event.Enter, func(id event.CID, _ interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { char := event.GetEntity(id).(*entities.Moving) char.Delta.Zero() if oak.IsDown(key.W) { @@ -126,7 +126,7 @@ func main() { return 0 }) - char.Bind(mouse.Press, func(id event.CID, me interface{}) int { + char.Bind(mouse.Press, func(id event.CallerID, me interface{}) int { char := event.GetEntity(id).(*entities.Moving) mevent := me.(*mouse.Event) x := char.X() + char.W/2 @@ -147,7 +147,7 @@ func main() { }) // Create enemies periodically - event.GlobalBind(event.Enter, func(_ event.CID, frames interface{}) int { + event.GlobalBind(event.Enter, func(_ event.CallerID, frames interface{}) int { enterPayload := frames.(event.EnterPayload) if enterPayload.FramesElapsed%EnemyRefresh == 0 { go NewEnemy() @@ -200,7 +200,7 @@ func NewEnemy() { enemy.UpdateLabel(Enemy) - enemy.Bind(event.Enter, func(id event.CID, _ interface{}) int { + enemy.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { enemy := event.GetEntity(id).(*entities.Solid) // move towards the player x, y := enemy.GetPos() @@ -223,7 +223,7 @@ func NewEnemy() { return 0 }) - enemy.Bind("Destroy", func(id event.CID, _ interface{}) int { + enemy.Bind("Destroy", func(id event.CallerID, _ interface{}) int { enemy := event.GetEntity(id).(*entities.Solid) enemy.Destroy() return 0 diff --git a/examples/top-down-shooter-tutorial/6-performance/performance.go b/examples/top-down-shooter-tutorial/6-performance/performance.go index 7cfaead4..112b89cc 100644 --- a/examples/top-down-shooter-tutorial/6-performance/performance.go +++ b/examples/top-down-shooter-tutorial/6-performance/performance.go @@ -81,7 +81,7 @@ func main() { float64(ctx.Window.Height()) / 2, } - char.Bind(event.Enter, func(id event.CID, payload interface{}) int { + char.Bind(event.Enter, func(id event.CallerID, payload interface{}) int { char := event.GetEntity(id).(*entities.Moving) enterPayload := payload.(event.EnterPayload) @@ -137,7 +137,7 @@ func main() { return 0 }) - char.Bind(mouse.Press, func(id event.CID, me interface{}) int { + char.Bind(mouse.Press, func(id event.CallerID, me interface{}) int { char := event.GetEntity(id).(*entities.Moving) mevent := me.(*mouse.Event) x := char.X() + char.W/2 @@ -158,7 +158,7 @@ func main() { }) // Create enemies periodically - event.GlobalBind(event.Enter, func(_ event.CID, frames interface{}) int { + event.GlobalBind(event.Enter, func(_ event.CallerID, frames interface{}) int { enterPayload := frames.(event.EnterPayload) if enterPayload.FramesElapsed%EnemyRefresh == 0 { go NewEnemy() @@ -222,7 +222,7 @@ func NewEnemy() { enemy.UpdateLabel(Enemy) - enemy.Bind(event.Enter, func(id event.CID, payload interface{}) int { + enemy.Bind(event.Enter, func(id event.CallerID, payload interface{}) int { enemy := event.GetEntity(id).(*entities.Solid) enterPayload := payload.(event.EnterPayload) // move towards the player @@ -246,7 +246,7 @@ func NewEnemy() { return 0 }) - enemy.Bind("Destroy", func(id event.CID, _ interface{}) int { + enemy.Bind("Destroy", func(id event.CallerID, _ interface{}) int { enemy := event.GetEntity(id).(*entities.Solid) enemy.Destroy() return 0 diff --git a/examples/zooming/main.go b/examples/zooming/main.go index c48ee22d..c166aaed 100644 --- a/examples/zooming/main.go +++ b/examples/zooming/main.go @@ -39,7 +39,7 @@ func main() { render.Draw(zoomer) // To illustrate zooming allow for arrow keys to control the main zoomable renderable. - event.GlobalBind(event.Enter, func(i event.CID, _ interface{}) int { + event.GlobalBind(event.Enter, func(i event.CallerID, _ interface{}) int { if oak.IsDown(key.UpArrow) { zoomOutFactorY *= .98 } diff --git a/inputTracker_test.go b/inputTracker_test.go index b8730c6c..3d84181e 100644 --- a/inputTracker_test.go +++ b/inputTracker_test.go @@ -23,7 +23,7 @@ func TestTrackInputChanges(t *testing.T) { expectedType := new(InputType) *expectedType = InputKeyboardMouse failed := false - c1.eventHandler.GlobalBind(event.InputChange, func(_ event.CID, payload interface{}) int { + c1.eventHandler.GlobalBind(event.InputChange, func(_ event.CallerID, payload interface{}) int { mode := payload.(InputType) if mode != *expectedType { failed = true diff --git a/mouse/onCollision_test.go b/mouse/onCollision_test.go index fbff918d..3041da8e 100644 --- a/mouse/onCollision_test.go +++ b/mouse/onCollision_test.go @@ -13,7 +13,7 @@ type cphase struct { CollisionPhase } -func (cp *cphase) Init() event.CID { +func (cp *cphase) Init() event.CallerID { return event.NextID(cp) } @@ -32,11 +32,11 @@ func TestCollisionPhase(t *testing.T) { t.Fatalf("phase collision errored") } var active bool - cid.Bind("MouseCollisionStart", func(event.CID, interface{}) int { + cid.Bind("MouseCollisionStart", func(event.CallerID, interface{}) int { active = true return 0 }) - cid.Bind("MouseCollisionStop", func(event.CID, interface{}) int { + cid.Bind("MouseCollisionStop", func(event.CallerID, interface{}) int { active = false return 0 }) diff --git a/render/interfaceFeatures.go b/render/interfaceFeatures.go index 14bdb756..1b8a8dfc 100644 --- a/render/interfaceFeatures.go +++ b/render/interfaceFeatures.go @@ -11,7 +11,7 @@ type NonStatic interface { // Triggerable types can have an ID set so when their animations finish, // they trigger AnimationEnd on that ID. type Triggerable interface { - SetTriggerID(event.CID) + SetTriggerID(event.CallerID) } type updates interface { diff --git a/render/logicfps.go b/render/logicfps.go index 8c06d81a..3df01103 100644 --- a/render/logicfps.go +++ b/render/logicfps.go @@ -10,7 +10,7 @@ import ( // LogicFPS is a Stackable that will draw the logical fps onto the screen when a part // of the draw stack. type LogicFPS struct { - event.CID + event.CallerID *Text fps int lastTime time.Time @@ -18,9 +18,10 @@ type LogicFPS struct { } // Init satisfies event.Entity -func (lf *LogicFPS) Init() event.CID { - id := event.NextID(lf) - lf.CID = id +func (lf *LogicFPS) Init() event.CallerID { + // TODO: not default caller map + id := event.DefaultCallerMap.Register(lf) + lf.CallerID = id return id } @@ -39,13 +40,16 @@ func NewLogicFPS(smoothing float64, font *Font, x, y float64) *LogicFPS { } lf.Text = font.NewIntText(&lf.fps, x, y) lf.Init() - lf.Bind(event.Enter, logicFPSBind) + // TODO: not default bus + event.Bind(event.DefaultBus, event.Enter, lf.CallerID, logicFPSBind) return lf } -func logicFPSBind(id event.CID, nothing interface{}) int { - lf := event.GetEntity(id).(*LogicFPS) +func logicFPSBind(id event.CallerID, _ event.EnterPayload) event.Response { + // TODO v4: should bindings give you an interface instead of a callerID, so bindings don't need to + // know what caller map to look up the caller from? + lf := event.DefaultCallerMap.GetEntity(id).(*LogicFPS) t := time.Now() lf.fps = int((timing.FPS(lf.lastTime, t) * lf.Smoothing) + (float64(lf.fps) * (1 - lf.Smoothing))) lf.lastTime = t diff --git a/render/particle/allocator.go b/render/particle/allocator.go index 1d6f80e9..83706c5d 100644 --- a/render/particle/allocator.go +++ b/render/particle/allocator.go @@ -10,24 +10,24 @@ const ( // An Allocator can allocate ids for particles type Allocator struct { - particleBlocks map[int]event.CID + particleBlocks map[int]event.CallerID nextOpenCh chan int freeCh chan int - allocCh chan event.CID + allocCh chan event.CallerID requestCh chan int - responseCh chan event.CID + responseCh chan event.CallerID stopCh chan struct{} } // NewAllocator creates a new allocator func NewAllocator() *Allocator { return &Allocator{ - particleBlocks: make(map[int]event.CID), + particleBlocks: make(map[int]event.CallerID), nextOpenCh: make(chan int), freeCh: make(chan int), - allocCh: make(chan event.CID), + allocCh: make(chan event.CallerID), requestCh: make(chan int), - responseCh: make(chan event.CID), + responseCh: make(chan event.CallerID), stopCh: make(chan struct{}), } } @@ -82,7 +82,7 @@ func (a *Allocator) freereceive(i int) int { } // Allocate requests a new block in the particle space for the given cid -func (a *Allocator) Allocate(id event.CID) int { +func (a *Allocator) Allocate(id event.CallerID) int { nextOpen := <-a.nextOpenCh a.allocCh <- id return nextOpen @@ -97,7 +97,8 @@ func (a *Allocator) Deallocate(block int) { func (a *Allocator) LookupSource(id int) *Source { a.requestCh <- id owner := <-a.responseCh - return event.GetEntity(owner).(*Source) + // TODO: not default? + return event.DefaultCallerMap.GetEntity(owner).(*Source) } // Lookup requests a specific particle in the particle space diff --git a/render/particle/allocator_test.go b/render/particle/allocator_test.go index e11c945f..0e616c3a 100644 --- a/render/particle/allocator_test.go +++ b/render/particle/allocator_test.go @@ -10,7 +10,7 @@ func TestAllocate(t *testing.T) { a := NewAllocator() go a.Run() for i := 0; i < 100; i++ { - if a.Allocate(event.CID(i)) != i { + if a.Allocate(event.CallerID(i)) != i { t.Fatalf("expected allocation of id %d to match id", i) } } diff --git a/render/particle/collisonGenerator.go b/render/particle/collisonGenerator.go index 983e30de..f4d269f5 100644 --- a/render/particle/collisonGenerator.go +++ b/render/particle/collisonGenerator.go @@ -49,7 +49,7 @@ func (cg *CollisionGenerator) GenerateParticle(bp *baseParticle) Particle { pos := p.GetPos() return &CollisionParticle{ p, - collision.NewReactiveSpace(collision.NewFullSpace(pos.X(), pos.Y(), w, h, 0, event.CID(bp.pID)), cg.HitMap), + collision.NewReactiveSpace(collision.NewFullSpace(pos.X(), pos.Y(), w, h, 0, event.CallerID(bp.pID)), cg.HitMap), } } diff --git a/render/particle/source.go b/render/particle/source.go index e1abe60d..722480d7 100644 --- a/render/particle/source.go +++ b/render/particle/source.go @@ -19,9 +19,11 @@ type Source struct { Generator Generator *Allocator + rotateBinding event.Binding + particles [blockSize]Particle nextPID int - CID event.CID + CID event.CallerID pIDBlock int stackLevel int EndFunc func() @@ -42,11 +44,12 @@ func NewSource(g Generator, stackLevel int) *Source { } // Init allows a source to be considered as an entity, and initializes it -func (ps *Source) Init() event.CID { - CID := event.NextID(ps) +func (ps *Source) Init() event.CallerID { + CID := event.DefaultCallerMap.Register(ps) ps.stopRotateAt = time.Now().Add( time.Duration(ps.Generator.GetBaseGenerator().Duration.Poll()) * time.Millisecond) - CID.Bind(event.Enter, rotateParticles) + + ps.rotateBinding = event.Bind(event.DefaultBus, event.Enter, CID, rotateParticles) ps.CID = CID ps.pIDBlock = ps.Allocate(ps.CID) return CID @@ -176,8 +179,8 @@ func (ps *Source) addParticles() { // rotateParticles updates particles over time as long // as a Source is active. -func rotateParticles(id event.CID, payload interface{}) int { - ps := id.E().(*Source) +func rotateParticles(id event.CallerID, _ event.EnterPayload) event.Response { + ps := event.DefaultCallerMap.GetEntity(id).(*Source) if ps.stopped { return 0 } @@ -197,33 +200,36 @@ func rotateParticles(id event.CID, payload interface{}) int { // clearParticles is used after a Source has been stopped // to continue moving old particles for as long as they exist. -func clearParticles(id event.CID, nothing interface{}) int { - if ps, ok := id.E().(*Source); ok { +func clearParticles(id event.CallerID, _ event.EnterPayload) event.Response { + iface := event.DefaultCallerMap.GetEntity(id) + if ps, ok := iface.(*Source); ok { if !ps.paused { if ps.cycleParticles() { } else { if ps.EndFunc != nil { ps.EndFunc() } - event.DestroyEntity(id) + // TODO: not default + event.DefaultCallerMap.DestroyEntity(id) ps.Deallocate(ps.pIDBlock) - return event.UnbindEvent + return event.UnbindThis } } return 0 } - return event.UnbindEvent + return event.UnbindThis } // Stop manually stops a Source, if its duration is infinite -// or if it should be stopped before expriring naturally. +// or if it should be stopped before expiring naturally. func (ps *Source) Stop() { if ps == nil { return } ps.stopped = true - ps.CID.UnbindAllAndRebind([]event.Bindable{clearParticles}, []string{event.Enter}) + ps.rotateBinding.Unbind() + event.Bind(event.DefaultBus, event.Enter, ps.CID, clearParticles) } // Pause on a Source just stops the repetition diff --git a/render/reverting.go b/render/reverting.go index c93d3823..bafa6d68 100644 --- a/render/reverting.go +++ b/render/reverting.go @@ -119,7 +119,7 @@ func (rv *Reverting) update() { } // SetTriggerID sets the ID AnimationEnd will trigger on for animating subtypes. -func (rv *Reverting) SetTriggerID(cid event.CID) { +func (rv *Reverting) SetTriggerID(cid event.CallerID) { if t, ok := rv.Modifiable.(Triggerable); ok { t.SetTriggerID(cid) } diff --git a/render/sequence.go b/render/sequence.go index d1a9bc9f..ef8731b5 100644 --- a/render/sequence.go +++ b/render/sequence.go @@ -20,7 +20,7 @@ type Sequence struct { lastChange time.Time sheetPos int frameTime int64 - cID event.CID + event.CallerID } // NewSequence returns a new sequence from the input modifiables, playing at @@ -68,18 +68,21 @@ func (sq *Sequence) Copy() Modifiable { return newSq } +var AnimationEnd = event.RegisterEvent[event.NoPayload]() + // SetTriggerID sets the ID that AnimationEnd will be triggered on when this // sequence loops over from its last frame to its first -func (sq *Sequence) SetTriggerID(id event.CID) { - sq.cID = id +func (sq *Sequence) SetTriggerID(id event.CallerID) { + sq.CallerID = id } func (sq *Sequence) update() { if sq.playing && time.Since(sq.lastChange).Nanoseconds() > sq.frameTime { sq.lastChange = time.Now() sq.sheetPos = (sq.sheetPos + 1) % len(sq.rs) - if sq.sheetPos == (len(sq.rs)-1) && sq.cID != 0 { - sq.cID.Trigger(event.AnimationEnd, nil) + if sq.sheetPos == (len(sq.rs)-1) && sq.CallerID != 0 { + // TODO: not default bus + event.TriggerForCallerOn(event.DefaultBus, sq.CallerID, AnimationEnd, event.NoPayload{}) } } } diff --git a/render/sequence_test.go b/render/sequence_test.go index 1a0c4ae4..aff13747 100644 --- a/render/sequence_test.go +++ b/render/sequence_test.go @@ -14,7 +14,7 @@ import ( type Dummy struct{} -func (d Dummy) Init() event.CID { +func (d Dummy) Init() event.CallerID { return event.NextID(d) } @@ -26,7 +26,7 @@ func TestSequenceTrigger(t *testing.T) { cid := Dummy{}.Init() sq.SetTriggerID(cid) triggerCh := make(chan struct{}) - cid.Bind(event.AnimationEnd, func(event.CID, interface{}) int { + cid.Bind(event.AnimationEnd, func(event.CallerID, interface{}) int { // This is a bad idea in real code, this will lock up // unbindings because the function that triggered this owns // the lock on the event bus until this function exits. diff --git a/render/switch.go b/render/switch.go index 9387205f..e493bc8a 100644 --- a/render/switch.go +++ b/render/switch.go @@ -187,7 +187,7 @@ func (c *Switch) IsStatic() bool { // Todo: standardize this with the other interface Set functions so that it // also only acts on the current subRenderable, or the other way around, or // somehow offer both options -func (c *Switch) SetTriggerID(cid event.CID) { +func (c *Switch) SetTriggerID(cid event.CallerID) { c.lock.RLock() for _, r := range c.subRenderables { if t, ok := r.(Triggerable); ok { diff --git a/window.go b/window.go index 06d434db..ce83ad81 100644 --- a/window.go +++ b/window.go @@ -357,3 +357,7 @@ func (w *Window) debugConsole(input io.Reader, output io.Writer) { debugstream.AttachToStream(w.ParentContext, input, output) debugstream.AddDefaultsForScope(w.ControllerID, w) } + +func (w *Window) GetCallerMap() *event.CallerMap { + return w.CallerMap +} diff --git a/window/window.go b/window/window.go index 8d13bae4..926df662 100644 --- a/window/window.go +++ b/window/window.go @@ -32,4 +32,5 @@ type Window interface { Quit() EventHandler() event.Handler + GetCallerMap() *event.CallerMap } diff --git a/window_test.go b/window_test.go index 23c15f79..44ea689f 100644 --- a/window_test.go +++ b/window_test.go @@ -14,7 +14,7 @@ func TestMouseClicks(t *testing.T) { sp := collision.NewFullSpace(0, 0, 100, 100, 1, 0) var triggered bool go event.ResolveChanges() - event.GlobalBind(mouse.Click, func(event.CID, interface{}) int { + event.GlobalBind(mouse.Click, func(event.CallerID, interface{}) int { triggered = true return 0 }) @@ -33,7 +33,7 @@ func TestMouseClicksRelative(t *testing.T) { sp := collision.NewFullSpace(0, 0, 100, 100, 1, 0) var triggered bool go c1.eventHandler.(*event.Bus).ResolveChanges() - c1.eventHandler.GlobalBind(mouse.ClickOn+"Relative", func(event.CID, interface{}) int { + c1.eventHandler.GlobalBind(mouse.ClickOn+"Relative", func(event.CallerID, interface{}) int { triggered = true return 0 }) @@ -49,7 +49,7 @@ func TestMouseClicksRelative(t *testing.T) { type ent struct{} -func (e ent) Init() event.CID { +func (e ent) Init() event.CallerID { return 0 } @@ -57,9 +57,9 @@ func TestPropagate(t *testing.T) { c1 := NewWindow() go event.ResolveChanges() var triggered bool - cid := event.CID(0).Parse(ent{}) + cid := event.CallerID(0).Parse(ent{}) s := collision.NewSpace(10, 10, 10, 10, cid) - s.CID.Bind("MouseDownOn", func(event.CID, interface{}) int { + s.CID.Bind("MouseDownOn", func(event.CallerID, interface{}) int { triggered = true return 0 }) From 2708f5cadf3216c810d8a3c117b79ea44a159638 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 19 Mar 2022 11:22:26 -0500 Subject: [PATCH 05/41] audio/pcm: drop writer js --- audio/pcm/writer_js.go | 142 ----------------------------------------- 1 file changed, 142 deletions(-) delete mode 100644 audio/pcm/writer_js.go diff --git a/audio/pcm/writer_js.go b/audio/pcm/writer_js.go deleted file mode 100644 index f6c0974e..00000000 --- a/audio/pcm/writer_js.go +++ /dev/null @@ -1,142 +0,0 @@ -//go:build js - -package pcm - -import ( - "fmt" - "math" - "sync" - "sync/atomic" - "syscall/js" - - "github.com/oakmound/oak/v3/oakerr" -) - -func initOS() error { - return nil -} - -var processorIndex int32 - -func newWriter(f Format) (Writer, error) { - if f.Bits != 32 { - return nil, oakerr.InvalidInput{ - InputName: "f.Bits", - } - } - window := js.Global() - actxConstruct := window.Get("AudioContext") - if actxConstruct.IsUndefined() || actxConstruct.IsNull() { - actxConstruct = window.Get("webkitAudioContext") - } - audioCtx := actxConstruct.New(map[string]interface{}{ - "latencyHint": "interactive", - "sampleRate": f.SampleRate, - }) - - processorName := "oakPCM" + strconv.Itoa(int(atomic.Add(&processorIndex, 1))) - window.Call("registerProcessor", processorName, "js class?") - audioCtx.Get("audioWorklet").Call("addModule", processorName) - - audioBuffer := audioCtx.Call("createBuffer", f.Channels, f.SampleRate*WriterBufferLengthInSeconds, f.SampleRate) - source := audioCtx.Call("createBufferSource") - - channelData := make([]js.Value, f.Channels) - for i := 0; i < int(f.Channels); i++ { - channelData[i] = audioBuffer.Call("getChannelData", i) - - } - - return &jsWriter{ - Format: f, - bufferSize: f.BytesPerSecond() * WriterBufferLengthInSeconds, - audioCtx: audioCtx, - buffer: audioBuffer, - channelData: channelData, - source: source, - }, nil -} - -type jsWriter struct { - sync.Mutex - Format - buffer js.Value - channelData []js.Value // Float32Array - source js.Value - audioCtx js.Value - lockedOffset uint32 - bufferSize uint32 - writeChannel int - writeOffset int - playing bool -} - -func (jsw *jsWriter) Close() error { - jsw.Lock() - defer jsw.Unlock() - - // we can't release this object? - if jsw.playing { - jsw.source.Call("stop") - } - return nil -} - -func (jsw *jsWriter) Reset() error { - jsw.Lock() - defer jsw.Unlock() - // emptyBuff := make([]byte, jsw.bufferSize) - // a, b, err := jsw.buff.LockBytes(0, jsw.bufferSize, 0) - // if err != nil { - // return err - // } - // copy(a, emptyBuff) - // if len(b) != 0 { - // copy(b, emptyBuff) - // } - // err = jsw.buff.UnlockBytes(a, b) - // jsw.Seek(0, io.SeekStart) - - //jsw.audioBuffer.Call("copyToChannel") - // make it a []float32 array somehow // then a byte array? or just convert - // from byte to float32 adaptively - return nil -} - -func (jsw *jsWriter) WritePCM(data []byte) (n int, err error) { - jsw.Lock() - defer jsw.Unlock() - - // we cannot write less than four bytes -- float32 - readAt := 0 - for len(data[readAt:]) >= 4 { - u32 := uint32(data[readAt]) + - uint32(data[readAt+1])<<8 + - uint32(data[readAt+2])<<16 + - uint32(data[readAt+3])<<24 - f32 := float32(u32) / float32(math.MaxInt32) - - jsw.channelData[jsw.writeChannel].SetIndex(jsw.writeOffset, f32) - - readAt += 4 - jsw.writeChannel++ - jsw.writeChannel %= int(jsw.Channels) - if jsw.writeChannel == 0 { - jsw.writeOffset++ - if jsw.writeOffset >= int((jsw.bufferSize/4)/uint32(jsw.Channels)) { - jsw.writeOffset = 0 - } - } - } - - jsw.source.Set("buffer", jsw.buffer) - if !jsw.playing { - fmt.Println("start playing") - jsw.playing = true - jsw.source.Set("loop", true) - jsw.source.Call("connect", jsw.audioCtx.Get("destination")) - jsw.source.Call("start") - } - - return readAt, nil -} From 7eefe21a03deeb2e359b1205a8f0ed8062d682c8 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Mon, 21 Mar 2022 20:34:39 -0500 Subject: [PATCH 06/41] event: move two three event type system: 1. unsafeBind(id,handler,interface{}), 2. globalBind(typedPayload) 3. bind(typedCaller,typedPayload) This migrates all packages but examples, entities, and joystickviz --- collision/attachSpace.go | 13 +++++--- collision/onCollision.go | 44 ++++++++++++------------- debugstream/scopeHelper.go | 6 ++-- debugtools/inputviz/keyboard.go | 25 +++++++------- debugtools/inputviz/mouse.go | 44 ++++++++++++------------- debugtools/mouse.go | 2 +- event/bind.go | 25 ++++++++++---- event/bus.go | 6 +++- event/caller.go | 14 +++++--- event/handler.go | 1 + event/internal.go | 2 +- inputTracker.go | 6 ++-- mouse/onCollision.go | 54 +++++++++++++++--------------- render/logicfps.go | 17 +++------- render/particle/source.go | 58 ++++++++++++++------------------- 15 files changed, 163 insertions(+), 154 deletions(-) diff --git a/collision/attachSpace.go b/collision/attachSpace.go index cf006bdf..42ab743e 100644 --- a/collision/attachSpace.go +++ b/collision/attachSpace.go @@ -26,7 +26,14 @@ func (as *AttachSpace) getAttachSpace() *AttachSpace { return as } +func (as *AttachSpace) CID() event.CallerID { + return (*as.aSpace).CID +} + +var _ attachSpace = &AttachSpace{} + type attachSpace interface { + event.Caller getAttachSpace() *AttachSpace } @@ -41,7 +48,7 @@ func Attach(v physics.Vector, s *Space, tree *Tree, offsets ...float64) error { if as.tree == nil { as.tree = DefaultTree } - as.binding = event.Bind(event.DefaultBus, event.Enter, s.CID, attachSpaceEnter) + as.binding = event.Bind(event.DefaultBus, event.Enter, t, attachSpaceEnter) if len(offsets) > 0 { as.offX = offsets[0] if len(offsets) > 1 { @@ -64,13 +71,11 @@ func Detach(s *Space) error { return errors.New("this space's entity is not composed of AttachSpace") } -func attachSpaceEnter(id event.CallerID, _ event.EnterPayload) event.Response { - asIface := event.DefaultCallerMap.GetEntity(id) +func attachSpaceEnter(asIface attachSpace, _ event.EnterPayload) event.Response { as := asIface.(attachSpace).getAttachSpace() x, y := as.follow.X()+as.offX, as.follow.Y()+as.offY if x != (*as.aSpace).X() || y != (*as.aSpace).Y() { - as.tree.UpdateSpace(x, y, (*as.aSpace).GetW(), (*as.aSpace).GetH(), *as.aSpace) } return 0 diff --git a/collision/onCollision.go b/collision/onCollision.go index 760f9996..67337a73 100644 --- a/collision/onCollision.go +++ b/collision/onCollision.go @@ -46,7 +46,7 @@ func PhaseCollisionWithBus(s *Space, tree *Tree, bus event.Handler, entities *ev if oc.tree == nil { oc.tree = DefaultTree } - event.Bind(bus, event.Enter, s.CID, phaseCollisionEnter(entities)) + bus.UnsafeBind(event.Enter.UnsafeEventID, s.CID, phaseCollisionEnter) return nil } return errors.New("This space's entity does not implement collisionPhase") @@ -58,33 +58,31 @@ var ( Stop = event.RegisterEvent[Label]() ) -func phaseCollisionEnter(entities *event.CallerMap) func(id event.CallerID, nothing event.EnterPayload) event.Response { - return func(id event.CallerID, _ event.EnterPayload) event.Response { - e := entities.GetEntity(id).(collisionPhase) - oc := e.getCollisionPhase() +func phaseCollisionEnter(id event.CallerID, handler event.Handler, _ interface{}) event.Response { + e := handler.GetCallerMap().GetEntity(id).(collisionPhase) + oc := e.getCollisionPhase() - // check hits - hits := oc.tree.Hits(oc.OnCollisionS) - newTouching := map[Label]bool{} + // check hits + hits := oc.tree.Hits(oc.OnCollisionS) + newTouching := map[Label]bool{} - // if any are new, trigger on collision - for _, h := range hits { - l := h.Label - if _, ok := oc.Touching[l]; !ok { - event.TriggerForCallerOn(oc.bus, id, Start, l) - } - newTouching[l] = true + // if any are new, trigger on collision + for _, h := range hits { + l := h.Label + if _, ok := oc.Touching[l]; !ok { + event.TriggerForCallerOn(oc.bus, id, Start, l) } + newTouching[l] = true + } - // if we lost any, trigger off collision - for l := range oc.Touching { - if _, ok := newTouching[l]; !ok { - event.TriggerForCallerOn(oc.bus, id, Stop, l) - } + // if we lost any, trigger off collision + for l := range oc.Touching { + if _, ok := newTouching[l]; !ok { + event.TriggerForCallerOn(handler, id, Stop, l) } + } - oc.Touching = newTouching + oc.Touching = newTouching - return 0 - } + return 0 } diff --git a/debugstream/scopeHelper.go b/debugstream/scopeHelper.go index 0d1b176b..7104b0b6 100644 --- a/debugstream/scopeHelper.go +++ b/debugstream/scopeHelper.go @@ -69,13 +69,13 @@ const explainMouseDetails = "the mext mouse click on the given window will print func mouseCommands(w window.Window) func([]string) string { return func(tokenString []string) string { - event.Bind(w.EventHandler(), mouse.Release, event.Global, mouseDetails(w)) + event.GlobalBind(w.EventHandler(), mouse.Release, mouseDetails(w)) return "" } } -func mouseDetails(w window.Window) func(event.CallerID, *mouse.Event) event.Response { - return func(nothing event.CallerID, me *mouse.Event) event.Response { +func mouseDetails(w window.Window) func(*mouse.Event) event.Response { + return func(me *mouse.Event) event.Response { viewPos := w.Viewport() x := int(me.X()) + viewPos[0] y := int(me.Y()) + viewPos[1] diff --git a/debugtools/inputviz/keyboard.go b/debugtools/inputviz/keyboard.go index 840cdbb8..4a905ac1 100644 --- a/debugtools/inputviz/keyboard.go +++ b/debugtools/inputviz/keyboard.go @@ -152,16 +152,17 @@ type Keyboard struct { ctx *scene.Context rs map[string]*render.Switch + + bindings []event.Binding } -func (k *Keyboard) Init() event.CallerID { - k.CID = k.ctx.CallerMap.NextID(k) - return k.CID +func (k *Keyboard) CID() event.CallerID { + return k.CallerID } func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { k.ctx = ctx - k.Init() + k.CallerID = k.ctx.CallerMap.Register(k) if k.Rect.W() == 0 || k.Rect.H() == 0 { k.Rect.Max = k.Rect.Min.Add(floatgeom.Point2{320, 180}) @@ -220,30 +221,30 @@ func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { } } - k.Bind(key.Down, key.Binding(func(id event.CallerID, ev key.Event) int { - kb, _ := k.ctx.CallerMap.GetEntity(id).(*Keyboard) + b1 := event.Bind(ctx.EventHandler, key.AnyDown, k, func(kb *Keyboard, ev key.Event) event.Response { btn := ev.Code.String()[4:] if kb.rs[btn] == nil { return 0 } kb.rs[btn].Set("pressed") return 0 - })) - k.Bind(key.Up, key.Binding(func(id event.CallerID, ev key.Event) int { - kb, _ := k.ctx.CallerMap.GetEntity(id).(*Keyboard) + }) + b2 := event.Bind(ctx.EventHandler, key.AnyUp, k, func(kb *Keyboard, ev key.Event) event.Response { btn := ev.Code.String()[4:] if kb.rs[btn] == nil { return 0 } kb.rs[btn].Set("released") return 0 - })) - + }) + k.bindings = []event.Binding{b1, b2} return nil } func (k *Keyboard) Destroy() { - k.UnbindAll() + for _, b := range k.bindings { + b.Unbind() + } for _, r := range k.rs { r.Undraw() } diff --git a/debugtools/inputviz/mouse.go b/debugtools/inputviz/mouse.go index 12964e6d..c07fcf2b 100644 --- a/debugtools/inputviz/mouse.go +++ b/debugtools/inputviz/mouse.go @@ -27,16 +27,18 @@ type Mouse struct { stateIncLock sync.RWMutex stateInc map[mouse.Button]int + + bindings []event.Binding } -func (m *Mouse) Init() event.CallerID { - m.CID = m.ctx.CallerMap.NextID(m) - return m.CID +func (m *Mouse) CID() event.CallerID { + return m.CallerID } func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { m.ctx = ctx - m.Init() + handler := ctx.EventHandler + m.CallerID = handler.GetCallerMap().Register(m) if m.Rect.W() == 0 || m.Rect.H() == 0 { m.Rect.Max = m.Rect.Min.Add(floatgeom.Point2{60, 100}) @@ -98,24 +100,21 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { ctx.DrawStack.Draw(m.posText, m.BaseLayer, layer+2) } - m.Bind(mouse.Press, mouse.Binding(func(id event.CallerID, ev *mouse.Event) int { - m, _ := m.ctx.CallerMap.GetEntity(id).(*Mouse) + b1 := event.Bind(handler, mouse.Press, m, func(m *Mouse, ev *mouse.Event) event.Response { m.rs[ev.Button].Set("pressed") m.stateIncLock.Lock() m.stateInc[ev.Button]++ m.stateIncLock.Unlock() return 0 - })) - m.Bind(mouse.Release, mouse.Binding(func(id event.CallerID, ev *mouse.Event) int { - m, _ := m.ctx.CallerMap.GetEntity(id).(*Mouse) + }) + b2 := event.Bind(handler, mouse.Release, m, func(m *Mouse, ev *mouse.Event) event.Response { m.rs[ev.Button].Set("released") m.stateIncLock.Lock() m.stateInc[ev.Button]++ m.stateIncLock.Unlock() return 0 - })) - m.Bind(mouse.ScrollDown, mouse.Binding(func(id event.CallerID, e *mouse.Event) int { - m, _ := m.ctx.CallerMap.GetEntity(id).(*Mouse) + }) + b3 := event.Bind(handler, mouse.ScrollDown, m, func(m *Mouse, ev *mouse.Event) event.Response { m.rs[mouse.ButtonMiddle].Set("scrolldown") m.stateIncLock.Lock() m.stateInc[mouse.ButtonMiddle]++ @@ -129,9 +128,8 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { m.stateIncLock.Unlock() }) return 0 - })) - m.Bind(mouse.ScrollUp, mouse.Binding(func(id event.CallerID, e *mouse.Event) int { - m, _ := m.ctx.CallerMap.GetEntity(id).(*Mouse) + }) + b4 := event.Bind(handler, mouse.ScrollUp, m, func(m *Mouse, ev *mouse.Event) event.Response { m.rs[mouse.ButtonMiddle].Set("scrollup") m.stateIncLock.Lock() m.stateInc[mouse.ButtonMiddle]++ @@ -145,13 +143,12 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { m.stateIncLock.Unlock() }) return 0 - })) - m.Bind(mouse.Drag, mouse.Binding(func(id event.CallerID, e *mouse.Event) int { - m, _ := m.ctx.CallerMap.GetEntity(id).(*Mouse) - m.lastMousePos.Point2 = e.Point2 + }) + b5 := event.Bind(handler, mouse.Drag, m, func(m *Mouse, ev *mouse.Event) event.Response { + m.lastMousePos.Point2 = ev.Point2 return 0 - })) - + }) + m.bindings = []event.Binding{b1, b2, b3, b4, b5} return nil } @@ -164,7 +161,10 @@ func (ps *posStringer) String() string { } func (m *Mouse) Destroy() { - m.UnbindAll() + // TODO: this is a lot of code to write to track and unbind all of an entity's bindings + for _, b := range m.bindings { + b.Unbind() + } for _, r := range m.rs { r.Undraw() } diff --git a/debugtools/mouse.go b/debugtools/mouse.go index 348ec851..7cd74b66 100644 --- a/debugtools/mouse.go +++ b/debugtools/mouse.go @@ -10,7 +10,7 @@ import ( // DebugMouseRelease will print the position and button pressed of the mouse when the mouse is released, if the given // key is held down at the time. If no key is given, it will always be printed func DebugMouseRelease(ctx *scene.Context, k string) { - event.Bind(ctx.EventHandler, mouse.Release, event.Global, func(_ event.CallerID, mev *mouse.Event) event.Response { + event.GlobalBind(ctx.EventHandler, mouse.Release, func(mev *mouse.Event) event.Response { if k == "" || ctx.KeyState.IsDown(k) { dlog.Info(mev) } diff --git a/event/bind.go b/event/bind.go index 807ec4e3..f1f692c9 100644 --- a/event/bind.go +++ b/event/bind.go @@ -81,20 +81,31 @@ func (bus *Bus) Unbind(loc Binding) { // A Bindable is a strongly typed callback function to be executed on Trigger. It must be paired // with an event registered via RegisterEvent. -type Bindable[T any] func(CallerID, T) Response +type Bindable[Payload any, C any] func(C, Payload) Response -func Bind[T any](b Handler, ev EventID[T], c CallerID, fn Bindable[T]) Binding { - return b.UnsafeBind(ev.UnsafeEventID, c, func(c CallerID, f interface{}) Response { - tf := f.(T) - return fn(c, tf) +func Bind[Payload any, C Caller](b Handler, ev EventID[Payload], c C, fn Bindable[Payload, C]) Binding { + return b.UnsafeBind(ev.UnsafeEventID, c.CID(), func(c CallerID, h Handler, payload interface{}) Response { + typedPayload := payload.(Payload) + ent := h.GetCallerMap().GetEntity(c) + typedEntity := ent.(C) + return fn(typedEntity, typedPayload) + }) +} + +type GlobalBindable[Payload any] func(Payload) Response + +func GlobalBind[Payload any](b Handler, ev EventID[Payload], fn GlobalBindable[Payload]) Binding { + return b.UnsafeBind(ev.UnsafeEventID, Global, func(c CallerID, h Handler, payload interface{}) Response { + typedPayload := payload.(Payload) + return fn(typedPayload) }) } // UnsafeBindable defines the underlying signature of all bindings. -type UnsafeBindable func(CallerID, interface{}) Response +type UnsafeBindable func(CallerID, Handler, interface{}) Response func EmptyBinding(f func()) UnsafeBindable { - return func(ci CallerID, i interface{}) Response { + return func(CallerID, Handler, interface{}) Response { f() return NoResponse } diff --git a/event/bus.go b/event/bus.go index 297d10a8..4dabda9c 100644 --- a/event/bus.go +++ b/event/bus.go @@ -118,4 +118,8 @@ func (bus *Bus) SetEnterLoopRate(frameDelay time.Duration) error { } bus.ticker.Reset(frameDelay) return nil -} \ No newline at end of file +} + +func (b *Bus) GetCallerMap() *CallerMap { + return b.callerMap +} diff --git a/event/caller.go b/event/caller.go index 9c4b59f0..c7869acc 100644 --- a/event/caller.go +++ b/event/caller.go @@ -10,13 +10,17 @@ type CallerID int64 const Global CallerID = 0 +type Caller interface { + CID() CallerID +} + // A CallerMap tracks CallerID mappings to Entities. // This is an alternative to passing in the entity via closure scoping, // and allows for more general bindings as simple top level functions. type CallerMap struct { highestID *int64 callersLock sync.RWMutex - callers map[CallerID]interface{} + callers map[CallerID]Caller } // NewCallerMap creates a caller map. A CallerMap @@ -24,14 +28,14 @@ type CallerMap struct { func NewCallerMap() *CallerMap { return &CallerMap{ highestID: new(int64), - callers: map[CallerID]interface{}{}, + callers: map[CallerID]Caller{}, } } // NextID finds the next available caller id // and returns it, after adding the given entity to // the caller map. -func (cm *CallerMap) Register(e interface{}) CallerID { +func (cm *CallerMap) Register(e Caller) CallerID { nextID := atomic.AddInt64(cm.highestID, 1) cm.callersLock.Lock() cm.callers[CallerID(nextID)] = e @@ -41,7 +45,7 @@ func (cm *CallerMap) Register(e interface{}) CallerID { // GetEntity returns the entity corresponding to the given ID within // the caller map. If no entity is found, it returns nil. -func (cm *CallerMap) GetEntity(id CallerID) interface{} { +func (cm *CallerMap) GetEntity(id CallerID) Caller { cm.callersLock.RLock() defer cm.callersLock.RUnlock() return cm.callers[id] @@ -67,6 +71,6 @@ func (cm *CallerMap) DestroyEntity(id CallerID) { func (cm *CallerMap) Reset() { cm.callersLock.Lock() *cm.highestID = 0 - cm.callers = map[CallerID]interface{}{} + cm.callers = map[CallerID]Caller{} cm.callersLock.Unlock() } diff --git a/event/handler.go b/event/handler.go index 7a64fd23..06f670d3 100644 --- a/event/handler.go +++ b/event/handler.go @@ -21,4 +21,5 @@ type Handler interface { UnsafeBind(UnsafeEventID, CallerID, UnsafeBindable) Binding Unbind(Binding) SetCallerMap(*CallerMap) + GetCallerMap() *CallerMap } diff --git a/event/internal.go b/event/internal.go index 9d1b9a8c..ba6d0ac0 100644 --- a/event/internal.go +++ b/event/internal.go @@ -39,7 +39,7 @@ func (bus *Bus) trigger(binds bindableList, eventID UnsafeEventID, callerID Call } go func() { if callerID == Global || bus.callerMap.HasEntity(callerID) { - response := bnd(callerID, data) + response := bnd(callerID, bus, data) switch response { case UnbindThis: // Q: Why does this call bus.Unbind when it already has the event index to delete? diff --git a/inputTracker.go b/inputTracker.go index 781096a4..0bf6b4c2 100644 --- a/inputTracker.go +++ b/inputTracker.go @@ -27,21 +27,21 @@ const ( ) func (w *Window) trackInputChanges() { - event.Bind(w.eventHandler, key.AnyDown, event.Global, func(event.CallerID, key.Event) event.Response { + event.GlobalBind(w.eventHandler, key.AnyDown, func(key.Event) event.Response { old := atomic.SwapInt32(&w.mostRecentInput, int32(InputKeyboard)) if InputType(old) != InputKeyboard { event.TriggerOn(w.eventHandler, InputChange, InputKeyboard) } return 0 }) - event.Bind(w.eventHandler, mouse.Press, event.Global, func(event.CallerID, *mouse.Event) event.Response { + event.GlobalBind(w.eventHandler, mouse.Press, func(*mouse.Event) event.Response { old := atomic.SwapInt32(&w.mostRecentInput, int32(InputMouse)) if InputType(old) != InputMouse { event.TriggerOn(w.eventHandler, InputChange, InputMouse) } return 0 }) - event.Bind(w.eventHandler, trackingJoystickChange, event.Global, func(event.CallerID, event.NoPayload) event.Response { + event.GlobalBind(w.eventHandler, trackingJoystickChange, func(event.NoPayload) event.Response { old := atomic.SwapInt32(&w.mostRecentInput, int32(InputMouse)) if InputType(old) != InputJoystick { event.TriggerOn(w.eventHandler, InputChange, InputJoystick) diff --git a/mouse/onCollision.go b/mouse/onCollision.go index 5e42cbae..be5803e1 100644 --- a/mouse/onCollision.go +++ b/mouse/onCollision.go @@ -37,7 +37,7 @@ func PhaseCollision(s *collision.Space, callerMap *event.CallerMap, handler even oc := cp.getCollisionPhase() oc.OnCollisionS = s oc.CallerMap = callerMap - event.Bind(handler, event.Enter, s.CID, phaseCollisionEnter(callerMap, handler)) + handler.UnsafeBind(event.Enter.UnsafeEventID, s.CID, phaseCollisionEnter) return nil } return errors.New("This space's entity does not implement collisionPhase") @@ -49,35 +49,35 @@ var ( Stop = event.RegisterEvent[*Event]() ) -func phaseCollisionEnter(callerMap *event.CallerMap, handler event.Handler) func(id event.CallerID, payload event.EnterPayload) event.Response { - return func(id event.CallerID, payload event.EnterPayload) event.Response { +func phaseCollisionEnter(id event.CallerID, handler event.Handler, _ interface{}) event.Response { + e, ok := handler.GetCallerMap().GetEntity(id).(collisionPhase) + if !ok { + return event.UnbindThis + } + oc := e.getCollisionPhase() + if oc == nil || oc.OnCollisionS == nil { + return 0 + } - e := callerMap.GetEntity(id).(collisionPhase) - oc := e.getCollisionPhase() - if oc == nil || oc.OnCollisionS == nil { - return 0 - } + // TODO: think about how this can more cleanly work with multiple windows + ev := oc.LastEvent + if ev == nil { + ev = &LastEvent + } + if ev.StopPropagation { + return 0 + } - // TODO: think about how this can more cleanly work with multiple windows - ev := oc.LastEvent - if ev == nil { - ev = &LastEvent + if oc.OnCollisionS.Contains(ev.ToSpace()) { + if !oc.wasTouching { + event.TriggerForCallerOn(handler, id, Start, ev) + oc.wasTouching = true } - if ev.StopPropagation { - return 0 + } else { + if oc.wasTouching { + event.TriggerForCallerOn(handler, id, Stop, ev) + oc.wasTouching = false } - - if oc.OnCollisionS.Contains(ev.ToSpace()) { - if !oc.wasTouching { - event.TriggerForCallerOn(handler, id, Start, ev) - oc.wasTouching = true - } - } else { - if oc.wasTouching { - event.TriggerForCallerOn(handler, id, Stop, ev) - oc.wasTouching = false - } - } - return 0 } + return 0 } diff --git a/render/logicfps.go b/render/logicfps.go index 3df01103..edce75c1 100644 --- a/render/logicfps.go +++ b/render/logicfps.go @@ -17,12 +17,8 @@ type LogicFPS struct { Smoothing float64 } -// Init satisfies event.Entity -func (lf *LogicFPS) Init() event.CallerID { - // TODO: not default caller map - id := event.DefaultCallerMap.Register(lf) - lf.CallerID = id - return id +func (lf LogicFPS) CID() event.CallerID { + return lf.CallerID } // NewLogicFPS returns a LogicFPS, which will render a counter of how fast it receives event.Enter events. @@ -39,17 +35,14 @@ func NewLogicFPS(smoothing float64, font *Font, x, y float64) *LogicFPS { lastTime: time.Now(), } lf.Text = font.NewIntText(&lf.fps, x, y) - lf.Init() + lf.CallerID = event.DefaultCallerMap.Register(lf) // TODO: not default bus - event.Bind(event.DefaultBus, event.Enter, lf.CallerID, logicFPSBind) + event.Bind(event.DefaultBus, event.Enter, lf, logicFPSBind) return lf } -func logicFPSBind(id event.CallerID, _ event.EnterPayload) event.Response { - // TODO v4: should bindings give you an interface instead of a callerID, so bindings don't need to - // know what caller map to look up the caller from? - lf := event.DefaultCallerMap.GetEntity(id).(*LogicFPS) +func logicFPSBind(lf *LogicFPS, _ event.EnterPayload) event.Response { t := time.Now() lf.fps = int((timing.FPS(lf.lastTime, t) * lf.Smoothing) + (float64(lf.fps) * (1 - lf.Smoothing))) lf.lastTime = t diff --git a/render/particle/source.go b/render/particle/source.go index 722480d7..6dab9391 100644 --- a/render/particle/source.go +++ b/render/particle/source.go @@ -21,9 +21,9 @@ type Source struct { rotateBinding event.Binding - particles [blockSize]Particle - nextPID int - CID event.CallerID + particles [blockSize]Particle + nextPID int + event.CallerID pIDBlock int stackLevel int EndFunc func() @@ -39,20 +39,18 @@ func NewSource(g Generator, stackLevel int) *Source { ps.Generator = g ps.stackLevel = stackLevel ps.Allocator = DefaultAllocator - ps.Init() - return ps -} - -// Init allows a source to be considered as an entity, and initializes it -func (ps *Source) Init() event.CallerID { - CID := event.DefaultCallerMap.Register(ps) + cid := event.DefaultCallerMap.Register(ps) ps.stopRotateAt = time.Now().Add( time.Duration(ps.Generator.GetBaseGenerator().Duration.Poll()) * time.Millisecond) - ps.rotateBinding = event.Bind(event.DefaultBus, event.Enter, CID, rotateParticles) - ps.CID = CID - ps.pIDBlock = ps.Allocate(ps.CID) - return CID + ps.rotateBinding = event.Bind(event.DefaultBus, event.Enter, ps, rotateParticles) + ps.CallerID = cid + ps.pIDBlock = ps.Allocate(ps.CallerID) + return ps +} + +func (ps *Source) CID() event.CallerID { + return ps.CallerID } func (ps *Source) cycleParticles() bool { @@ -179,8 +177,7 @@ func (ps *Source) addParticles() { // rotateParticles updates particles over time as long // as a Source is active. -func rotateParticles(id event.CallerID, _ event.EnterPayload) event.Response { - ps := event.DefaultCallerMap.GetEntity(id).(*Source) +func rotateParticles(ps *Source, _ event.EnterPayload) event.Response { if ps.stopped { return 0 } @@ -200,25 +197,20 @@ func rotateParticles(id event.CallerID, _ event.EnterPayload) event.Response { // clearParticles is used after a Source has been stopped // to continue moving old particles for as long as they exist. -func clearParticles(id event.CallerID, _ event.EnterPayload) event.Response { - iface := event.DefaultCallerMap.GetEntity(id) - if ps, ok := iface.(*Source); ok { - if !ps.paused { - if ps.cycleParticles() { - } else { - if ps.EndFunc != nil { - ps.EndFunc() - } - // TODO: not default - event.DefaultCallerMap.DestroyEntity(id) - ps.Deallocate(ps.pIDBlock) - return event.UnbindThis +func clearParticles(ps *Source, _ event.EnterPayload) event.Response { + if !ps.paused { + if ps.cycleParticles() { + } else { + if ps.EndFunc != nil { + ps.EndFunc() } + // TODO: not default + event.DefaultCallerMap.DestroyEntity(ps.CID()) + ps.Deallocate(ps.pIDBlock) + return event.UnbindThis } - - return 0 } - return event.UnbindThis + return 0 } // Stop manually stops a Source, if its duration is infinite @@ -229,7 +221,7 @@ func (ps *Source) Stop() { } ps.stopped = true ps.rotateBinding.Unbind() - event.Bind(event.DefaultBus, event.Enter, ps.CID, clearParticles) + event.Bind(event.DefaultBus, event.Enter, ps, clearParticles) } // Pause on a Source just stops the repetition From 64f205281fb7462b89a28f4f8af38f09d33da899 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Mon, 21 Mar 2022 20:57:29 -0500 Subject: [PATCH 07/41] scene: remove loop - adjusts the loading scene to not use a loop. - keyboard-viz example working --- debugtools/inputviz/joystick.go | 62 ++++++++++++--------------------- examples/keyboard-viz/main.go | 3 ++ init.go | 9 +---- joystick/joystick.go | 5 +-- loading.go | 2 +- scene/map.go | 3 -- scene/scene.go | 16 --------- sceneLoop.go | 15 +++++--- window.go | 9 +---- 9 files changed, 41 insertions(+), 83 deletions(-) diff --git a/debugtools/inputviz/joystick.go b/debugtools/inputviz/joystick.go index 9834c873..f8fea3b3 100644 --- a/debugtools/inputviz/joystick.go +++ b/debugtools/inputviz/joystick.go @@ -9,6 +9,8 @@ import ( "math" "time" + mkey "golang.org/x/mobile/event/key" + "github.com/oakmound/oak/v3/alg/floatgeom" "github.com/oakmound/oak/v3/dlog" "github.com/oakmound/oak/v3/event" @@ -55,11 +57,12 @@ type Joystick struct { lStickCenter floatgeom.Point2 rStickCenter floatgeom.Point2 cancel func() + + bindings []event.Binding } -func (j *Joystick) Init() event.CallerID { - j.CID = j.ctx.CallerMap.NextID(j) - return j.CID +func (j *Joystick) CID() event.CallerID { + return j.CallerID } func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, layer int) error { @@ -76,7 +79,7 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l j.rs = make(map[string]render.Modifiable) j.lastState = &joystick.State{} j.ctx = ctx - j.Init() + j.CallerID = ctx.CallerMap.Register(j) j.rs["Outline"] = outline j.rs["LtStick"] = render.NewCircle(color.RGBA{255, 255, 255, 255}, 15, 12) j.rs["RtStick"] = render.NewCircle(color.RGBA{255, 255, 255, 255}, 15, 12) @@ -180,19 +183,22 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l joystick.InputRightShoulder, } - j.CheckedIDBind(joystick.Disconnected, func(rend *Joystick, _ uint32) { + b1 := event.Bind(ctx.EventHandler, joystick.Disconnected, j, func(rend *Joystick, _ uint32) event.Response { j.Destroy() + return 0 }) - j.CheckedBind(key.Down+key.Spacebar, func(rend *Joystick, st *joystick.State) { + // TODO: it is bad that you need to import two 'key' packages + b2 := event.Bind(ctx.EventHandler, key.Down(mkey.CodeSpacebar), j, func(j *Joystick, _ key.Event) event.Response { j.joy.Vibrate(math.MaxUint16, math.MaxUint16) go func() { time.Sleep(1 * time.Second) j.joy.Vibrate(0, 0) }() + return 0 }) - j.CheckedBind(joystick.Change, func(rend *Joystick, st *joystick.State) { + b3 := event.Bind(ctx.EventHandler, joystick.Change, j, func(j *Joystick, st *joystick.State) event.Response { for _, inputB := range bts { b := string(inputB) r := j.rs[b] @@ -211,60 +217,36 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l tgr = "RtTrigger" x = j.rs[tgr].X() j.rs[tgr].SetPos(x, j.triggerY+float64(st.TriggerR/16)) + return 0 }) - j.CheckedBind(joystick.LtStickChange, func(rend *Joystick, st *joystick.State) { + b4 := event.Bind(ctx.EventHandler, joystick.LtStickChange, j, func(j *Joystick, st *joystick.State) event.Response { pos := j.lStickCenter pos = pos.Add(floatgeom.Point2{ float64(st.StickLX / 2048), -float64(st.StickLY / 2048), }) j.rs["LtStick"].SetPos(pos.X(), pos.Y()) + return 0 }) - j.CheckedBind(joystick.RtStickChange, func(rend *Joystick, st *joystick.State) { + b5 := event.Bind(ctx.EventHandler, joystick.RtStickChange, j, func(j *Joystick, st *joystick.State) event.Response { pos := j.rStickCenter pos = pos.Add(floatgeom.Point2{ float64(st.StickRX / 2048), -float64(st.StickRY / 2048), }) j.rs["RtStick"].SetPos(pos.X(), pos.Y()) - }) - return nil -} - -func (j *Joystick) CheckedIDBind(ev string, f func(*Joystick, uint32)) { - j.Bind(ev, func(id event.CallerID, jid interface{}) int { - joy, ok := event.GetEntity(id).(*Joystick) - if !ok { - return 0 - } - n, ok := jid.(uint32) - if !ok { - return 0 - } - f(joy, n) - return 0 - }) -} - -func (j *Joystick) CheckedBind(ev string, f func(*Joystick, *joystick.State)) { - j.Bind(ev, func(id event.CallerID, state interface{}) int { - joy, ok := event.GetEntity(id).(*Joystick) - if !ok { - return 0 - } - st, ok := state.(*joystick.State) - if !ok { - return 0 - } - f(joy, st) return 0 }) + j.bindings = []event.Binding{b1, b2, b3, b4, b5} + return nil } func (j *Joystick) Destroy() { - j.UnbindAll() + for _, b := range j.bindings { + b.Unbind() + } for _, r := range j.rs { r.Undraw() } diff --git a/examples/keyboard-viz/main.go b/examples/keyboard-viz/main.go index ce10b8cc..4ba204f7 100644 --- a/examples/keyboard-viz/main.go +++ b/examples/keyboard-viz/main.go @@ -8,6 +8,7 @@ import ( "github.com/oakmound/oak/v3" "github.com/oakmound/oak/v3/alg/floatgeom" "github.com/oakmound/oak/v3/debugtools/inputviz" + "github.com/oakmound/oak/v3/dlog" "github.com/oakmound/oak/v3/render" "github.com/oakmound/oak/v3/scene" ) @@ -15,6 +16,7 @@ import ( func main() { oak.AddScene("keyviz", scene.Scene{ Start: func(ctx *scene.Context) { + fmt.Println("start") fnt, _ := render.DefFontGenerator.RegenerateWith(func(fg render.FontGenerator) render.FontGenerator { fg.Color = image.NewUniform(color.RGBA{0, 0, 0, 255}) fg.Size = 13 @@ -30,6 +32,7 @@ func main() { }, }) err := oak.Init("keyviz", func(c oak.Config) (oak.Config, error) { + c.Debug.Level = dlog.VERBOSE.String() c.Screen.Width = 800 c.Screen.Height = 300 return c, nil diff --git a/init.go b/init.go index e2ab5694..d7aa2291 100644 --- a/init.go +++ b/init.go @@ -74,14 +74,7 @@ func (w *Window) Init(firstScene string, configOptions ...ConfigOption) error { overrideInit(w) - go w.sceneLoop(firstScene, w.config.TrackInputChanges) - if w.config.BatchLoad { - w.startupLoading = true - go func() { - w.loadAssets(w.config.Assets.ImagePath, w.config.Assets.AudioPath) - w.endLoad() - }() - } + go w.sceneLoop(firstScene, w.config.TrackInputChanges, w.config.BatchLoad) if w.config.EnableDebugConsole { go w.debugConsole(os.Stdin, os.Stdout) } diff --git a/joystick/joystick.go b/joystick/joystick.go index f2ed83ef..1500dc15 100644 --- a/joystick/joystick.go +++ b/joystick/joystick.go @@ -31,7 +31,7 @@ const ( InputRightStick Input = "RightStick" ) -// Events. All events include a *State payload. +// Events. All events but Disconnected include a *State payload. var ( Change = event.RegisterEvent[*State]() ButtonDown = event.RegisterEvent[*State]() @@ -40,7 +40,8 @@ var ( LtTriggerChange = event.RegisterEvent[*State]() RtStickChange = event.RegisterEvent[*State]() LtStickChange = event.RegisterEvent[*State]() - Disconnected = event.RegisterEvent[*State]() + // Disconnected includes the ID of the joystick that disconnected. + Disconnected = event.RegisterEvent[uint32]() ) // Init calls any os functions necessary to detect joysticks diff --git a/loading.go b/loading.go index 7293da61..70c68e8c 100644 --- a/loading.go +++ b/loading.go @@ -38,7 +38,7 @@ func (w *Window) loadAssets(imageDir, audioDir string) { func (w *Window) endLoad() { dlog.Verb("Done Loading") - w.startupLoading = false + w.NextScene() } // SetFS updates all calls oak or oak's subpackages will make to read from the given filesystem. diff --git a/scene/map.go b/scene/map.go index 6687d260..deaa77e8 100644 --- a/scene/map.go +++ b/scene/map.go @@ -48,9 +48,6 @@ func (m *Map) AddScene(name string, s Scene) error { if s.Start == nil { s.Start = func(*Context) {} } - if s.Loop == nil { - s.Loop = func() bool { return true } - } if s.End == nil { s.End = GoTo(name) } diff --git a/scene/scene.go b/scene/scene.go index 2e7d7420..5f5cf641 100644 --- a/scene/scene.go +++ b/scene/scene.go @@ -12,9 +12,6 @@ type Scene struct { // what scene came before this one and a direct reference to clean data structures // for event handling and rendering Start func(ctx *Context) - // If Loop returns true, the scene will continue - // If Loop returns false, End will be called to determine the next scene - Loop func() (cont bool) // End is a function returning the next scene and a SceneResult of // input settings for the next scene. End func() (nextScene string, result *Result) @@ -27,19 +24,6 @@ type Result struct { Transition } -// BooleanLoop returns a Loop function that will end a scene as soon as the -// input boolean is false, resetting it to true in the process for the -// next scene -func BooleanLoop(b *bool) func() (cont bool) { - return func() bool { - if !(*b) { - *b = true - return false - } - return true - } -} - // GoTo returns an End function that, without any other customization possible, // will change to the input next scene. func GoTo(nextScene string) func() (nextScene string, result *Result) { diff --git a/sceneLoop.go b/sceneLoop.go index c2fe80dc..5b07452f 100644 --- a/sceneLoop.go +++ b/sceneLoop.go @@ -14,10 +14,17 @@ import ( // for preloading assets const oakLoadingScene = "oak:loading" -func (w *Window) sceneLoop(first string, trackingInputs bool) { +func (w *Window) sceneLoop(first string, trackingInputs, batchLoad bool) { w.SceneMap.AddScene(oakLoadingScene, scene.Scene{ - Loop: func() bool { - return w.startupLoading + Start: func(ctx *scene.Context) { + if batchLoad { + go func() { + w.loadAssets(w.config.Assets.ImagePath, w.config.Assets.AudioPath) + w.endLoad() + }() + } else { + go w.endLoad() + } }, End: func() (string, *scene.Result) { return w.firstScene, &scene.Result{ @@ -98,8 +105,6 @@ func (w *Window) sceneLoop(first string, trackingInputs bool) { case <-w.quitCh: cancel() return - case <-w.sceneCh: - cont = scen.Loop() case nextSceneOverride = <-w.skipSceneCh: cont = false } diff --git a/window.go b/window.go index ce83ad81..9f109d07 100644 --- a/window.go +++ b/window.go @@ -43,11 +43,6 @@ type Window struct { // TODO: most of these channels are not closed cleanly transitionCh chan struct{} - // The Scene channel receives a signal - // when a scene's .loop() function should - // be called. - sceneCh chan struct{} - // The skip scene channel receives a debug // signal to forcibly go to the next // scene. @@ -157,8 +152,7 @@ type Window struct { exitError error ParentContext context.Context - startupLoading bool - useViewBounds bool + useViewBounds bool // UseAspectRatio determines whether new window changes will distort or // maintain the relative width to height ratio of the screen buffer. UseAspectRatio bool @@ -175,7 +169,6 @@ func NewWindow() *Window { c := &Window{ State: key.NewState(), transitionCh: make(chan struct{}), - sceneCh: make(chan struct{}), skipSceneCh: make(chan string), quitCh: make(chan struct{}), drawCh: make(chan struct{}), From f5d5052cdeebb3b14b263954198de02f767d9724 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Tue, 22 Mar 2022 06:44:01 -0500 Subject: [PATCH 08/41] entities: convert to event v4 --- entities/doodad.go | 42 +++++++++++------------------------------ entities/interactive.go | 19 ++++++++++--------- entities/moving.go | 18 ++++++++++-------- entities/reactive.go | 18 ++++++++++-------- entities/solid.go | 22 +++++++++++---------- 5 files changed, 53 insertions(+), 66 deletions(-) diff --git a/entities/doodad.go b/entities/doodad.go index 55b2bb51..bef8722a 100644 --- a/entities/doodad.go +++ b/entities/doodad.go @@ -21,39 +21,19 @@ func NewDoodad(x, y float64, r render.Renderable, cid event.CallerID) *Doodad { if r != nil { r.SetPos(x, y) } - d := Doodad{} + d := &Doodad{} d.Point = *NewPoint(x, y) d.R = r - d.CID = cid.Parse(&d) - return &d -} - -// Init satisfies event.Entity -func (d *Doodad) Init() event.CallerID { - d.CID = event.NextID(d) - return d.CID -} - -// GetID returns this Doodad's CID -// Consider: are these getters needed? -func (d *Doodad) GetID() event.CallerID { - return d.CID -} - -// GetRenderable returns this Doodad's Renderable -func (d *Doodad) GetRenderable() render.Renderable { - return d.R + if cid == 0 { + d.CallerID = event.DefaultCallerMap.Register(d) + } else { + d.CallerID = cid + } + return d } -// SetRenderable sets this Doodad's renderable, drawing it. -// Todo:this automatic drawing doesn't really work with our -// two tiers of draw layers -func (d *Doodad) SetRenderable(r render.Renderable) { - if d.R != nil { - d.R.Undraw() - } - d.R = r - render.Draw(d.R, d.R.GetLayer()) +func (d *Doodad) CID() event.CallerID { + return d.CallerID } // Destroy cleans up the events, renderable and @@ -62,8 +42,8 @@ func (d *Doodad) Destroy() { if d.R != nil { d.R.Undraw() } - d.CID.UnbindAll() - event.DestroyEntity(d.CID) + event.DefaultBus.UnbindAllFrom(d.CallerID) + event.DefaultCallerMap.DestroyEntity(d.CallerID) } // Overwrites diff --git a/entities/interactive.go b/entities/interactive.go index 12f3a450..1a24c77c 100644 --- a/entities/interactive.go +++ b/entities/interactive.go @@ -17,20 +17,21 @@ type Interactive struct { func NewInteractive(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid event.CallerID, friction float64) *Interactive { - i := Interactive{} - cid = cid.Parse(&i) - i.Reactive = *NewReactive(x, y, w, h, r, tree, cid) + i := &Interactive{} + if cid == 0 { + i.CallerID = event.DefaultCallerMap.Register(i) + } else { + i.CallerID = cid + } + i.Reactive = *NewReactive(x, y, w, h, r, tree, i.CallerID) i.vMoving = vMoving{ Delta: physics.NewVector(0, 0), Speed: physics.NewVector(0, 0), Friction: friction, } - return &i + return i } -// Init satisfies event.Entity -func (iv *Interactive) Init() event.CallerID { - cID := event.NextID(iv) - iv.CID = cID - return cID +func (i *Interactive) CID() event.CallerID { + return i.CallerID } diff --git a/entities/moving.go b/entities/moving.go index fe4968a0..62b3a575 100644 --- a/entities/moving.go +++ b/entities/moving.go @@ -15,21 +15,23 @@ type Moving struct { // NewMoving returns a new Moving func NewMoving(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid event.CallerID, friction float64) *Moving { - m := Moving{} - cid = cid.Parse(&m) - m.Solid = *NewSolid(x, y, w, h, r, tree, cid) + m := &Moving{} + if cid == 0 { + m.CallerID = event.DefaultCallerMap.Register(m) + } else { + m.CallerID = cid + } + m.Solid = *NewSolid(x, y, w, h, r, tree, m.CallerID) m.vMoving = vMoving{ Delta: physics.NewVector(0, 0), Speed: physics.NewVector(0, 0), Friction: friction, } - return &m + return m } -// Init satisfies event.Entity -func (m *Moving) Init() event.CallerID { - m.CID = event.NextID(m) - return m.CID +func (m *Moving) CID() event.CallerID { + return m.CallerID } // ShiftVector probably shouldn't be on moving but it lets you diff --git a/entities/reactive.go b/entities/reactive.go index 8cea369f..d6f0a85f 100644 --- a/entities/reactive.go +++ b/entities/reactive.go @@ -18,9 +18,13 @@ type Reactive struct { // NewReactive returns a new Reactive struct. The added space will // be added to the input tree, or DefTree if none is given. func NewReactive(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid event.CallerID) *Reactive { - rct := Reactive{} - cid = cid.Parse(&rct) - rct.Doodad = *NewDoodad(x, y, r, cid) + rct := &Reactive{} + if cid == 0 { + rct.CallerID = event.DefaultCallerMap.Register(rct) + } else { + rct.CallerID = cid + } + rct.Doodad = *NewDoodad(x, y, r, rct.CallerID) rct.W = w rct.H = h rct.RSpace = collision.NewReactiveSpace(collision.NewSpace(x, y, w, h, cid), map[collision.Label]collision.OnHit{}) @@ -30,7 +34,7 @@ func NewReactive(x, y, w, h float64, r render.Renderable, tree *collision.Tree, rct.RSpace.Tree = tree rct.Tree = tree rct.Tree.Add(rct.RSpace.Space) - return &rct + return rct } // SetDim sets the dimensions of this reactive's space and it's logical dimensions @@ -72,10 +76,8 @@ func (r *Reactive) GetReactiveSpace() *collision.ReactiveSpace { // Overwrites -// Init satisfies event.Entity -func (r *Reactive) Init() event.CallerID { - r.CID = event.NextID(r) - return r.CID +func (r *Reactive) CID() event.CallerID { + return r.CallerID } // ShiftPos acts like SetPos if given r.X()+x, r.Y()+y diff --git a/entities/solid.go b/entities/solid.go index ee104334..934ce90b 100644 --- a/entities/solid.go +++ b/entities/solid.go @@ -18,9 +18,13 @@ type Solid struct { // belongs to the given collision tree. If nil is given as the tree, it will // belong to collision.DefTree func NewSolid(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid event.CallerID) *Solid { - s := Solid{} - cid = cid.Parse(&s) - s.Doodad = *NewDoodad(x, y, r, cid) + s := &Solid{} + if cid == 0 { + s.CallerID = event.DefaultCallerMap.Register(s) + } else { + s.CallerID = cid + } + s.Doodad = *NewDoodad(x, y, r, s.CallerID) s.W = w s.H = h if tree == nil { @@ -29,7 +33,11 @@ func NewSolid(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid s.Tree = tree s.Space = collision.NewSpace(x, y, w, h, cid) s.Tree.Add(s.Space) - return &s + return s +} + +func (s *Solid) CID() event.CallerID { + return s.CallerID } // SetDim sets the logical dimensions of the solid and the real @@ -98,12 +106,6 @@ func (s *Solid) HitLabel(classtype collision.Label) *collision.Space { // Overwrites -// Init satisfies event.Entity -func (s *Solid) Init() event.CallerID { - s.CID = event.NextID(s) - return s.CID -} - // SetPos sets the position of the collision space, the logical position, // and the renderable position of the solid. func (s *Solid) SetPos(x float64, y float64) { From e5facb652565c93d5bb4771c329314f51f09c143 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Tue, 22 Mar 2022 06:49:08 -0500 Subject: [PATCH 09/41] event: add unbind all helper, fix ticker and bindable list logic --- event/bind.go | 10 ++++++++++ event/bus.go | 4 ++-- event/handler.go | 1 + event/internal.go | 2 +- examples/pong/main.go | 23 +++++++++++------------ 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/event/bind.go b/event/bind.go index f1f692c9..55c88723 100644 --- a/event/bind.go +++ b/event/bind.go @@ -110,3 +110,13 @@ func EmptyBinding(f func()) UnsafeBindable { return NoResponse } } + +func (bus *Bus) UnbindAllFrom(c CallerID) { + go func() { + bus.mutex.Lock() + for _, callerMap := range bus.bindingMap { + delete(callerMap, c) + } + bus.mutex.Unlock() + }() +} diff --git a/event/bus.go b/event/bus.go index 4dabda9c..e99ef700 100644 --- a/event/bus.go +++ b/event/bus.go @@ -73,10 +73,11 @@ func (bus *Bus) EnterLoop(frameDelay time.Duration) { bus.framesElapsed = 0 if bus.ticker == nil { bus.ticker = time.NewTicker(frameDelay) + } else { + bus.ticker.Reset(frameDelay) } bus.doneCh = make(chan struct{}) go func() { - bus.ticker.Reset(frameDelay) frameDelayF64 := float64(frameDelay) lastTick := time.Now() for { @@ -101,7 +102,6 @@ func (bus *Bus) EnterLoop(frameDelay time.Duration) { func (bus *Bus) Stop() error { if bus.ticker != nil { bus.ticker.Stop() - bus.ticker = nil } close(bus.doneCh) return nil diff --git a/event/handler.go b/event/handler.go index 06f670d3..c5a13613 100644 --- a/event/handler.go +++ b/event/handler.go @@ -20,6 +20,7 @@ type Handler interface { Trigger(event UnsafeEventID, data interface{}) chan struct{} UnsafeBind(UnsafeEventID, CallerID, UnsafeBindable) Binding Unbind(Binding) + UnbindAllFrom(CallerID) SetCallerMap(*CallerMap) GetCallerMap() *CallerMap } diff --git a/event/internal.go b/event/internal.go index ba6d0ac0..a27fcbb3 100644 --- a/event/internal.go +++ b/event/internal.go @@ -21,7 +21,7 @@ func (eb *Bus) getBindableList(eventID UnsafeEventID, callerID CallerID) bindabl } bl := eb.bindingMap[eventID][callerID] if bl == nil { - bl := make(bindableList) + bl = make(bindableList) eb.bindingMap[eventID][callerID] = bl } return bl diff --git a/examples/pong/main.go b/examples/pong/main.go index 7ce8154e..24dea2a3 100644 --- a/examples/pong/main.go +++ b/examples/pong/main.go @@ -25,10 +25,10 @@ const ( func main() { oak.AddScene("pong", - scene.Scene{Start: func(*scene.Context) { - newPaddle(20, 200, 1) - newPaddle(600, 200, 2) - newBall(320, 240) + scene.Scene{Start: func(ctx *scene.Context) { + newPaddle(ctx, 20, 200, 1) + newPaddle(ctx, 600, 200, 2) + newBall(ctx, 320, 240) render.Draw(render.DefaultFont().NewIntText(&score2, 200, 20), 3) render.Draw(render.DefaultFont().NewIntText(&score1, 400, 20), 3) }}) @@ -38,10 +38,10 @@ func main() { }) } -func newBall(x, y float64) { +func newBall(ctx *scene.Context, x, y float64) { b := entities.NewMoving(x, y, 10, 10, render.NewColorBoxR(10, 10, color.RGBA{255, 255, 255, 255}), nil, 0, 0) render.Draw(b.R, 2) - b.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { + event.GlobalBind(ctx.EventHandler, event.Enter, func(_ event.EnterPayload) event.Response { if b.Delta.X() == 0 && b.Delta.Y() == 0 { b.Delta.SetY((rand.Float64() - 0.5) * 4) b.Delta.SetX((rand.Float64() - 0.5) * 16) @@ -70,21 +70,20 @@ func newBall(x, y float64) { }) } -func newPaddle(x, y float64, player int) { +func newPaddle(ctx *scene.Context, x, y float64, player int) { p := entities.NewMoving(x, y, 20, 100, render.NewColorBoxR(20, 100, color.RGBA{255, 255, 255, 255}), nil, 0, 0) p.Speed.SetY(8) render.Draw(p.R, 1) p.Space.UpdateLabel(hitPaddle) if player == 1 { - p.Bind(event.Enter, enterPaddle(key.UpArrow, key.DownArrow)) + event.Bind(ctx.EventHandler, event.Enter, p, enterPaddle(key.UpArrow, key.DownArrow)) } else { - p.Bind(event.Enter, enterPaddle(key.W, key.S)) + event.Bind(ctx.EventHandler, event.Enter, p, enterPaddle(key.W, key.S)) } } -func enterPaddle(up, down string) func(event.CallerID, interface{}) int { - return func(id event.CallerID, nothing interface{}) int { - p := id.E().(*entities.Moving) +func enterPaddle(up, down string) func(*entities.Moving, event.EnterPayload) event.Response { + return func(p *entities.Moving, _ event.EnterPayload) event.Response { p.Delta.SetY(0) if oak.IsDown(up) { p.Delta.SetY(-p.Speed.Y()) From 38bef9dfbb36904ad062c1215e04a2a19f14e193 Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 26 Mar 2022 09:08:31 -0400 Subject: [PATCH 10/41] examples: Update a few examples without dependencies to have the new binding system --- examples/bezier/main.go | 30 ++++++++++++++-------------- examples/click-propagation/main.go | 32 ++++++++++++------------------ examples/collision-demo/main.go | 19 +++++++++--------- 3 files changed, 38 insertions(+), 43 deletions(-) diff --git a/examples/bezier/main.go b/examples/bezier/main.go index fd78d848..92a65afe 100644 --- a/examples/bezier/main.go +++ b/examples/bezier/main.go @@ -53,23 +53,23 @@ func main() { return "" }}) - oak.AddScene("bezier", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("bezier", scene.Scene{Start: func(ctx *scene.Context) { mouseFloats := []float64{} - event.GlobalBind(mouse.Press, func(_ event.CallerID, mouseEvent interface{}) int { - me := mouseEvent.(*mouse.Event) - // Left click to add a point to the curve - if me.Button == mouse.ButtonLeft { - mouseFloats = append(mouseFloats, float64(me.X()), float64(me.Y())) - renderCurve(mouseFloats) - // Perform any other click to reset the drawn curve - } else { - mouseFloats = []float64{} - if cmp != nil { - cmp.Undraw() + event.GlobalBind(ctx.EventHandler, + mouse.Press, func(me *mouse.Event) event.Response { + // Left click to add a point to the curve + if me.Button == mouse.ButtonLeft { + mouseFloats = append(mouseFloats, float64(me.X()), float64(me.Y())) + renderCurve(mouseFloats) + // Perform any other click to reset the drawn curve + } else { + mouseFloats = []float64{} + if cmp != nil { + cmp.Undraw() + } } - } - return 0 - }) + return 0 + }) }}) oak.Init("bezier", func(c oak.Config) (oak.Config, error) { c.EnableDebugConsole = true diff --git a/examples/click-propagation/main.go b/examples/click-propagation/main.go index ba3304bc..2cf97753 100644 --- a/examples/click-propagation/main.go +++ b/examples/click-propagation/main.go @@ -26,7 +26,7 @@ func main() { for x := 20.0; x < 400; x += 20 { z++ y -= 20 - newHoverButton(x, y, 35, 35, color.RGBA{200, 200, 200, 200}, z) + newHoverButton(ctx, x, y, 35, 35, color.RGBA{200, 200, 200, 200}, z) } }, }) @@ -40,44 +40,38 @@ type hoverButton struct { *changingColorBox } -func (hb *hoverButton) Init() event.CallerID { - hb.id = event.NextID(hb) +func (hb *hoverButton) CID() event.CallerID { return hb.id } -func newHoverButton(x, y, w, h float64, clr color.RGBA, layer int) { +func newHoverButton(ctx *scene.Context, x, y, w, h float64, clr color.RGBA, layer int) { hb := &hoverButton{} - hb.Init() + hb.id = ctx.CallerMap.Register(hb) hb.changingColorBox = newChangingColorBox(x, y, int(w), int(h), clr) sp := collision.NewSpace(x, y, w, h, hb.id) sp.SetZLayer(float64(layer)) mouse.Add(sp) - mouse.PhaseCollision(sp) + mouse.PhaseCollision(sp, ctx.EventHandler.GetCallerMap(), ctx.EventHandler) render.Draw(hb.changingColorBox, 0, layer) - hb.id.Bind(mouse.ClickOn, func(c event.CallerID, i interface{}) int { - hb := event.GetEntity(c).(*hoverButton) - me := i.(*mouse.Event) - fmt.Println(c, me.Point2) - hb.changingColorBox.c = color.RGBA{128, 128, 128, 128} + + event.Bind(ctx.EventHandler, mouse.Click, hb, func(box *hoverButton, me *mouse.Event) event.Response { + fmt.Println(box, me.Point2) + box.changingColorBox.c = color.RGBA{128, 128, 128, 128} me.StopPropagation = true return 0 }) - hb.id.Bind(mouse.Start, func(c event.CallerID, i interface{}) int { + event.Bind(ctx.EventHandler, mouse.Start, hb, func(box *hoverButton, me *mouse.Event) event.Response { fmt.Println("start") - hb := event.GetEntity(c).(*hoverButton) - me := i.(*mouse.Event) - hb.changingColorBox.c = color.RGBA{50, 50, 50, 50} + box.changingColorBox.c = color.RGBA{50, 50, 50, 50} me.StopPropagation = true return 0 }) - hb.id.Bind(mouse.Stop, func(c event.CallerID, i interface{}) int { + event.Bind(ctx.EventHandler, mouse.Stop, hb, func(box *hoverButton, me *mouse.Event) event.Response { fmt.Println("stop") - hb := event.GetEntity(c).(*hoverButton) - me := i.(*mouse.Event) - hb.changingColorBox.c = clr + box.changingColorBox.c = clr me.StopPropagation = true return 0 }) diff --git a/examples/collision-demo/main.go b/examples/collision-demo/main.go index 5b5e5843..4d32722e 100644 --- a/examples/collision-demo/main.go +++ b/examples/collision-demo/main.go @@ -20,13 +20,13 @@ const ( ) func main() { - oak.AddScene("demo", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("demo", scene.Scene{Start: func(ctx *scene.Context) { act := &AttachCollisionTest{} - act.Solid = entities.NewSolid(50, 50, 50, 50, render.NewColorBox(50, 50, color.RGBA{0, 0, 0, 255}), nil, act.Init()) + act.Solid = entities.NewSolid(50, 50, 50, 50, render.NewColorBox(50, 50, color.RGBA{0, 0, 0, 255}), nil, ctx.CallerMap.Register(act)) collision.Attach(act.Vector, act.Space, nil, 0, 0) - act.Bind(event.Enter, func(event.CallerID, interface{}) int { + event.Bind(ctx.EventHandler, event.Enter, act, func(act *AttachCollisionTest, ev event.EnterPayload) event.Response { if act.ShouldUpdate { act.ShouldUpdate = false act.R.Undraw() @@ -56,8 +56,8 @@ func main() { render.Draw(act.R, 0, 1) collision.PhaseCollision(act.Space, nil) - act.Bind(collision.Start, func(id event.CallerID, label interface{}) int { - l := label.(collision.Label) + + event.Bind(ctx.EventHandler, collision.Start, act, func(act *AttachCollisionTest, l collision.Label) event.Response { switch l { case RED: act.r += 125 @@ -75,8 +75,7 @@ func main() { } return 0 }) - act.Bind(collision.Stop, func(id event.CallerID, label interface{}) int { - l := label.(collision.Label) + event.Bind(ctx.EventHandler, collision.Stop, act, func(act *AttachCollisionTest, l collision.Label) event.Response { switch l { case RED: act.r -= 125 @@ -136,10 +135,12 @@ type AttachCollisionTest struct { nextR render.Renderable } -func (act *AttachCollisionTest) Init() event.CallerID { - return event.NextID(act) +// CID returns the event.CallerID so that this can be bound to. +func (act *AttachCollisionTest) CID() event.CallerID { + return act.CallerID } +// UpdateR with the rgb set on the act. func (act *AttachCollisionTest) UpdateR() { act.nextR = render.NewColorBox(50, 50, color.RGBA{uint8(act.r), uint8(act.g), uint8(act.b), 255}) act.nextR.SetPos(act.X(), act.Y()) From 480e791f1a9e0f939f323429d6a5d076310ebf12 Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 26 Mar 2022 12:08:27 -0400 Subject: [PATCH 11/41] event: Update Bindable signature for better error messages --- event/bind.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/event/bind.go b/event/bind.go index 55c88723..9a9c9854 100644 --- a/event/bind.go +++ b/event/bind.go @@ -81,9 +81,9 @@ func (bus *Bus) Unbind(loc Binding) { // A Bindable is a strongly typed callback function to be executed on Trigger. It must be paired // with an event registered via RegisterEvent. -type Bindable[Payload any, C any] func(C, Payload) Response +type Bindable[C any, Payload any] func(C, Payload) Response -func Bind[Payload any, C Caller](b Handler, ev EventID[Payload], c C, fn Bindable[Payload, C]) Binding { +func Bind[C Caller, Payload any](b Handler, ev EventID[Payload], c C, fn Bindable[C, Payload]) Binding { return b.UnsafeBind(ev.UnsafeEventID, c.CID(), func(c CallerID, h Handler, payload interface{}) Response { typedPayload := payload.(Payload) ent := h.GetCallerMap().GetEntity(c) From 02e311e229939921044a19364ef1405db9bf9b5e Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 26 Mar 2022 16:35:26 -0500 Subject: [PATCH 12/41] oak: fix main package tests for event package changes --- collision/attachSpace_test.go | 1 - collision/onCollision_test.go | 1 - config.go | 34 +------------ config_test.go | 62 +---------------------- event/bind.go | 18 ++++--- event/caller.go | 7 ++- inputTracker_test.go | 17 +++---- mouse/onCollision_test.go | 1 - render/sequence_test.go | 1 - testdata/default.config | 3 +- window_test.go | 94 ++++++++++++++++++++--------------- 11 files changed, 83 insertions(+), 156 deletions(-) diff --git a/collision/attachSpace_test.go b/collision/attachSpace_test.go index 2c81e019..d46fbca6 100644 --- a/collision/attachSpace_test.go +++ b/collision/attachSpace_test.go @@ -18,7 +18,6 @@ func (as *aspace) Init() event.CallerID { func TestAttachSpace(t *testing.T) { Clear() - go event.ResolveChanges() go func() { for { <-time.After(5 * time.Millisecond) diff --git a/collision/onCollision_test.go b/collision/onCollision_test.go index 9ca0ee2e..6c3de14b 100644 --- a/collision/onCollision_test.go +++ b/collision/onCollision_test.go @@ -19,7 +19,6 @@ func (cp *cphase) Init() event.CallerID { func TestCollisionPhase(t *testing.T) { callers := event.NewCallerMap() bus := event.NewBus(callers) - go bus.ResolveChanges() go func() { for { <-time.After(5 * time.Millisecond) diff --git a/config.go b/config.go index 5a47ed32..6e0a14b3 100644 --- a/config.go +++ b/config.go @@ -2,9 +2,7 @@ package oak import ( "encoding/json" - "errors" "io" - "time" "github.com/oakmound/oak/v3/fileutil" "github.com/oakmound/oak/v3/shiny/driver" @@ -31,37 +29,7 @@ type Config struct { Borderless bool `json:"borderless"` Fullscreen bool `json:"fullscreen"` SkipRNGSeed bool `json:"skip_rng_seed"` - UnlimitedDrawFrameRate bool `json:"unlimitedDrawFrameRate` -} - -// A Duration is a wrapper around time.Duration that allows for easier json formatting. -type Duration time.Duration - -// MarshalJSON writes a duration as json. -func (d Duration) MarshalJSON() ([]byte, error) { - return json.Marshal(time.Duration(d).String()) -} - -// UnmarshalJSON extracts a duration from a json byte slice. -func (d *Duration) UnmarshalJSON(b []byte) error { - var v interface{} - if err := json.Unmarshal(b, &v); err != nil { - return err - } - switch value := v.(type) { - case float64: - *d = Duration(time.Duration(value)) - return nil - case string: - tmp, err := time.ParseDuration(value) - if err != nil { - return err - } - *d = Duration(tmp) - return nil - default: - return errors.New("invalid duration type") - } + UnlimitedDrawFrameRate bool `json:"unlimitedDrawFrameRate"` } // NewConfig creates a config from a set of transformation options. diff --git a/config_test.go b/config_test.go index 0bbf9a92..bff8501c 100644 --- a/config_test.go +++ b/config_test.go @@ -5,7 +5,6 @@ import ( "os" "path/filepath" "testing" - "time" ) func TestDefaultConfigFileMatchesEmptyConfig(t *testing.T) { @@ -47,7 +46,6 @@ func configEquals(c1, c2 Config) bool { IdleDrawFrameRate int `json:"idleDrawFrameRate"` Language string `json:"language"` Title string `json:"title"` - EventRefreshRate Duration `json:"refreshRate"` BatchLoad bool `json:"batchLoad"` GestureSupport bool `json:"gestureSupport"` LoadBuiltinCommands bool `json:"loadBuiltinCommands"` @@ -57,7 +55,7 @@ func configEquals(c1, c2 Config) bool { Borderless bool `json:"borderless"` Fullscreen bool `json:"fullscreen"` SkipRNGSeed bool `json:"skip_rng_seed"` - UnlimitedDrawFrameRate bool `json:"unlimitedDrawFrameRate` + UnlimitedDrawFrameRate bool `json:"unlimitedDrawFrameRate"` } cc1 := comparableConfig{ Assets: c1.Assets, @@ -69,7 +67,6 @@ func configEquals(c1, c2 Config) bool { IdleDrawFrameRate: c1.IdleDrawFrameRate, Language: c1.Language, Title: c1.Title, - EventRefreshRate: c1.EventRefreshRate, BatchLoad: c1.BatchLoad, GestureSupport: c1.GestureSupport, LoadBuiltinCommands: c1.LoadBuiltinCommands, @@ -91,7 +88,6 @@ func configEquals(c1, c2 Config) bool { IdleDrawFrameRate: c2.IdleDrawFrameRate, Language: c2.Language, Title: c2.Title, - EventRefreshRate: c2.EventRefreshRate, BatchLoad: c2.BatchLoad, GestureSupport: c2.GestureSupport, LoadBuiltinCommands: c2.LoadBuiltinCommands, @@ -144,59 +140,3 @@ func TestReaderConfigBadJSON(t *testing.T) { // This error is an stdlib error, not ours, so we don't care // about its type } - -func TestDuration_HappyPath(t *testing.T) { - d := Duration(time.Second) - marshalled, err := d.MarshalJSON() - if err != nil { - t.Fatalf("marshal duration failed: %v", err) - } - d2 := new(Duration) - err = d2.UnmarshalJSON(marshalled) - if err != nil { - t.Fatalf("unmarshal failed: %v", err) - } - marshalled2, err := d2.MarshalJSON() - if err != nil { - t.Fatalf("marshal duration 2 failed: %v", err) - } - if !bytes.Equal(marshalled, marshalled2) { - t.Fatalf("marshals not equal: %v vs %v", string(marshalled), string(marshalled2)) - } -} - -func TestDuration_UnmarshalJSON_Float(t *testing.T) { - f := []byte("10.0") - d2 := new(Duration) - err := d2.UnmarshalJSON(f) - if err != nil { - t.Fatalf("unmarshal failed: %v", err) - } -} - -func TestDuration_UnmarshalJSON_Boolean(t *testing.T) { - f := []byte("false") - d2 := new(Duration) - err := d2.UnmarshalJSON(f) - if err == nil { - t.Fatalf("expected failure in unmarshal") - } -} - -func TestDuration_UnmarshalJSON_BadString(t *testing.T) { - f := []byte("\"10mmmm\"") - d2 := new(Duration) - err := d2.UnmarshalJSON(f) - if err == nil { - t.Fatalf("expected failure in unmarshal") - } -} - -func TestDuration_UnmarshalJSON_BadJSON(t *testing.T) { - f := []byte("\"1mm") - d2 := new(Duration) - err := d2.UnmarshalJSON(f) - if err == nil { - t.Fatalf("expected failure in unmarshal") - } -} diff --git a/event/bind.go b/event/bind.go index 55c88723..6e1da0c3 100644 --- a/event/bind.go +++ b/event/bind.go @@ -22,6 +22,9 @@ type Binding struct { EventID UnsafeEventID CallerID CallerID BindID BindID + // Bound is closed once the binding has been applied. Wait on this condition carefully; bindings + // will not take effect while an event is being triggered (e.g. in a even callback's returning thread) + Bound <-chan struct{} } // Unbind unbinds the callback associated with this binding from it's own event handler. If this binding @@ -39,16 +42,19 @@ type BindID int64 // call is 'unsafe' because UnsafeBindables use bare interface{} types. func (bus *Bus) UnsafeBind(eventID UnsafeEventID, callerID CallerID, fn UnsafeBindable) Binding { bindID := BindID(atomic.AddInt64(bus.nextBindID, 1)) + ch := make(chan struct{}) go func() { bus.mutex.Lock() bus.getBindableList(eventID, callerID).storeBindable(fn, bindID) bus.mutex.Unlock() + close(ch) }() return Binding{ Handler: bus, EventID: eventID, CallerID: callerID, BindID: bindID, + Bound: ch, } } @@ -81,12 +87,12 @@ func (bus *Bus) Unbind(loc Binding) { // A Bindable is a strongly typed callback function to be executed on Trigger. It must be paired // with an event registered via RegisterEvent. -type Bindable[Payload any, C any] func(C, Payload) Response +type Bindable[C any, Payload any] func(C, Payload) Response -func Bind[Payload any, C Caller](b Handler, ev EventID[Payload], c C, fn Bindable[Payload, C]) Binding { - return b.UnsafeBind(ev.UnsafeEventID, c.CID(), func(c CallerID, h Handler, payload interface{}) Response { +func Bind[C Caller, Payload any](h Handler, ev EventID[Payload], caller C, fn Bindable[C, Payload]) Binding { + return h.UnsafeBind(ev.UnsafeEventID, caller.CID(), func(cid CallerID, h Handler, payload interface{}) Response { typedPayload := payload.(Payload) - ent := h.GetCallerMap().GetEntity(c) + ent := h.GetCallerMap().GetEntity(cid) typedEntity := ent.(C) return fn(typedEntity, typedPayload) }) @@ -94,8 +100,8 @@ func Bind[Payload any, C Caller](b Handler, ev EventID[Payload], c C, fn Bindabl type GlobalBindable[Payload any] func(Payload) Response -func GlobalBind[Payload any](b Handler, ev EventID[Payload], fn GlobalBindable[Payload]) Binding { - return b.UnsafeBind(ev.UnsafeEventID, Global, func(c CallerID, h Handler, payload interface{}) Response { +func GlobalBind[Payload any](h Handler, ev EventID[Payload], fn GlobalBindable[Payload]) Binding { + return h.UnsafeBind(ev.UnsafeEventID, Global, func(cid CallerID, h Handler, payload interface{}) Response { typedPayload := payload.(Payload) return fn(typedPayload) }) diff --git a/event/caller.go b/event/caller.go index c7869acc..09ca69bd 100644 --- a/event/caller.go +++ b/event/caller.go @@ -5,9 +5,14 @@ import ( "sync/atomic" ) -// A CallerID is a caller ID that entities use to trigger and bind functionality +// A CallerID is a caller ID that Callers use to bind themselves to receive callback +// signals when given events are triggered type CallerID int64 +func (c CallerID) CID() CallerID { + return c +} + const Global CallerID = 0 type Caller interface { diff --git a/inputTracker_test.go b/inputTracker_test.go index 3d84181e..322374e9 100644 --- a/inputTracker_test.go +++ b/inputTracker_test.go @@ -5,7 +5,6 @@ import ( "time" "github.com/oakmound/oak/v3/event" - "github.com/oakmound/oak/v3/joystick" "github.com/oakmound/oak/v3/key" "github.com/oakmound/oak/v3/mouse" "github.com/oakmound/oak/v3/scene" @@ -13,7 +12,7 @@ import ( func TestTrackInputChanges(t *testing.T) { c1 := NewWindow() - c1.SetLogicHandler(event.NewBus(nil)) + c1.SetLogicHandler(event.NewBus(event.NewCallerMap())) c1.AddScene("1", scene.Scene{}) go c1.Init("1", func(c Config) (Config, error) { c.TrackInputChanges = true @@ -21,10 +20,9 @@ func TestTrackInputChanges(t *testing.T) { }) time.Sleep(2 * time.Second) expectedType := new(InputType) - *expectedType = InputKeyboardMouse + *expectedType = InputKeyboard failed := false - c1.eventHandler.GlobalBind(event.InputChange, func(_ event.CallerID, payload interface{}) int { - mode := payload.(InputType) + event.GlobalBind(c1.eventHandler, InputChange, func(mode InputType) event.Response { if mode != *expectedType { failed = true } @@ -36,18 +34,19 @@ func TestTrackInputChanges(t *testing.T) { t.Fatalf("keyboard change failed") } *expectedType = InputJoystick - c1.eventHandler.Trigger("Tracking"+joystick.Change, &joystick.State{}) + event.TriggerOn(c1.eventHandler, trackingJoystickChange, struct{}{}) time.Sleep(2 * time.Second) if failed { t.Fatalf("joystick change failed") } - *expectedType = InputKeyboardMouse - c1.TriggerMouseEvent(mouse.Event{Event: mouse.Press}) + *expectedType = InputMouse + c1.TriggerMouseEvent(mouse.Event{EventType: mouse.Press}) time.Sleep(2 * time.Second) if failed { t.Fatalf("mouse change failed") } - c1.mostRecentInput = InputJoystick + *expectedType = InputKeyboard + c1.mostRecentInput = int32(InputJoystick) c1.TriggerKeyDown(key.Event{}) time.Sleep(2 * time.Second) if failed { diff --git a/mouse/onCollision_test.go b/mouse/onCollision_test.go index 3041da8e..5afedb90 100644 --- a/mouse/onCollision_test.go +++ b/mouse/onCollision_test.go @@ -18,7 +18,6 @@ func (cp *cphase) Init() event.CallerID { } func TestCollisionPhase(t *testing.T) { - go event.ResolveChanges() go func() { for { <-time.After(5 * time.Millisecond) diff --git a/render/sequence_test.go b/render/sequence_test.go index aff13747..aaf13778 100644 --- a/render/sequence_test.go +++ b/render/sequence_test.go @@ -22,7 +22,6 @@ func TestSequenceTrigger(t *testing.T) { sq := NewSequence(5, NewColorBox(10, 10, color.RGBA{255, 0, 0, 255}), NewColorBox(10, 10, color.RGBA{0, 255, 0, 255})) - go event.ResolveChanges() cid := Dummy{}.Init() sq.SetTriggerID(cid) triggerCh := make(chan struct{}) diff --git a/testdata/default.config b/testdata/default.config index bdab51fe..61b8427d 100644 --- a/testdata/default.config +++ b/testdata/default.config @@ -22,6 +22,5 @@ "language": "English", "title": "Oak Window", "batchLoad": false, - "gestureSupport": false, - "refreshRate": "50ms" + "gestureSupport": false } \ No newline at end of file diff --git a/window_test.go b/window_test.go index 44ea689f..cdd02d7b 100644 --- a/window_test.go +++ b/window_test.go @@ -11,69 +11,83 @@ import ( func TestMouseClicks(t *testing.T) { c1 := NewWindow() - sp := collision.NewFullSpace(0, 0, 100, 100, 1, 0) - var triggered bool - go event.ResolveChanges() - event.GlobalBind(mouse.Click, func(event.CallerID, interface{}) int { - triggered = true + c1.MouseTree = collision.NewTree() + ch := make(chan struct{}) + c1.eventHandler = event.NewBus(event.NewCallerMap()) + bnd := event.GlobalBind(c1.eventHandler, mouse.Click, func(_ *mouse.Event) event.Response { + close(ch) return 0 }) - time.Sleep(2 * time.Second) - mouse.DefaultTree.Add(sp) + select { + case <-time.After(2 * time.Second): + t.Fatalf("click binding never bound") + case <-bnd.Bound: + } + sp := collision.NewFullSpace(0, 0, 100, 100, 1, 0) + c1.MouseTree.Add(sp) c1.Propagate(mouse.PressOn, mouse.NewEvent(5, 5, mouse.ButtonLeft, mouse.PressOn)) c1.Propagate(mouse.ReleaseOn, mouse.NewEvent(5, 5, mouse.ButtonLeft, mouse.ReleaseOn)) - time.Sleep(2 * time.Second) - if !triggered { + select { + case <-time.After(2 * time.Second): t.Fatalf("propagation failed to trigger click binding") + case <-ch: } } func TestMouseClicksRelative(t *testing.T) { c1 := NewWindow() - sp := collision.NewFullSpace(0, 0, 100, 100, 1, 0) - var triggered bool - go c1.eventHandler.(*event.Bus).ResolveChanges() - c1.eventHandler.GlobalBind(mouse.ClickOn+"Relative", func(event.CallerID, interface{}) int { - triggered = true + c1.MouseTree = collision.NewTree() + ch := make(chan struct{}) + c1.eventHandler = event.NewBus(event.NewCallerMap()) + bnd := event.GlobalBind(c1.eventHandler, mouse.RelativeClickOn, func(_ *mouse.Event) event.Response { + close(ch) return 0 }) - time.Sleep(2 * time.Second) + select { + case <-time.After(2 * time.Second): + t.Fatalf("click binding never bound") + case <-bnd.Bound: + } + sp := collision.NewFullSpace(0, 0, 100, 100, 1, 0) c1.MouseTree.Add(sp) - c1.Propagate(mouse.PressOn+"Relative", mouse.NewEvent(5, 5, mouse.ButtonLeft, mouse.PressOn)) - c1.Propagate(mouse.ReleaseOn+"Relative", mouse.NewEvent(5, 5, mouse.ButtonLeft, mouse.ReleaseOn)) - time.Sleep(3 * time.Second) - if !triggered { + defer c1.MouseTree.Clear() + c1.Propagate(mouse.RelativePressOn, mouse.NewEvent(5, 5, mouse.ButtonLeft, mouse.PressOn)) + c1.Propagate(mouse.RelativeReleaseOn, mouse.NewEvent(5, 5, mouse.ButtonLeft, mouse.ReleaseOn)) + select { + case <-time.After(2 * time.Second): t.Fatalf("propagation failed to trigger click binding") + case <-ch: } } -type ent struct{} - -func (e ent) Init() event.CallerID { - return 0 +type ent struct { + event.CallerID } func TestPropagate(t *testing.T) { c1 := NewWindow() - go event.ResolveChanges() - var triggered bool - cid := event.CallerID(0).Parse(ent{}) - s := collision.NewSpace(10, 10, 10, 10, cid) - s.CID.Bind("MouseDownOn", func(event.CallerID, interface{}) int { - triggered = true + c1.eventHandler = event.NewBus(event.NewCallerMap()) + + thisEnt := ent{} + thisEnt.CallerID = c1.eventHandler.GetCallerMap().Register(thisEnt) + ch := make(chan struct{}) + s := collision.NewSpace(10, 10, 10, 10, thisEnt.CallerID) + event.Bind(c1.eventHandler, mouse.PressOn, thisEnt, func(ent, *mouse.Event) event.Response { + close(ch) return 0 }) - mouse.Add(s) - time.Sleep(200 * time.Millisecond) - c1.Propagate("MouseUpOn", mouse.NewEvent(15, 15, mouse.ButtonLeft, "MouseUp")) - time.Sleep(200 * time.Millisecond) - if triggered { - t.Fatalf("mouse up triggered binding") + c1.MouseTree = collision.NewTree() + c1.MouseTree.Add(s) + c1.Propagate(mouse.ReleaseOn, mouse.NewEvent(15, 15, mouse.ButtonLeft, mouse.Release)) + select { + case <-ch: + t.Fatalf("release propagation triggered press binding") + case <-time.After(1 * time.Second): } - time.Sleep(200 * time.Millisecond) - c1.Propagate("MouseDownOn", mouse.NewEvent(15, 15, mouse.ButtonLeft, "MouseDown")) - time.Sleep(200 * time.Millisecond) - if !triggered { - t.Fatalf("mouse down failed to trigger binding") + c1.Propagate(mouse.PressOn, mouse.NewEvent(15, 15, mouse.ButtonLeft, mouse.Press)) + select { + case <-time.After(2 * time.Second): + t.Fatalf("propagation failed to trigger press binding") + case <-ch: } } From 7d75b183e524886e3f97d9f6832715ebf153b087 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 26 Mar 2022 17:43:35 -0500 Subject: [PATCH 13/41] collision: update tests for new event api --- collision/attachSpace.go | 14 +++++++++--- collision/attachSpace_test.go | 22 +++++++++---------- collision/onCollision.go | 13 +++++++----- collision/onCollision_test.go | 40 ++++++++++++++++------------------- 4 files changed, 48 insertions(+), 41 deletions(-) diff --git a/collision/attachSpace.go b/collision/attachSpace.go index 42ab743e..e1a7a9e3 100644 --- a/collision/attachSpace.go +++ b/collision/attachSpace.go @@ -39,7 +39,11 @@ type attachSpace interface { // Attach attaches v to the given space with optional x,y offsets. See AttachSpace. func Attach(v physics.Vector, s *Space, tree *Tree, offsets ...float64) error { - en := event.DefaultCallerMap.GetEntity(s.CID) + return AttachWithBus(v, s, tree, event.DefaultBus, offsets...) +} + +func AttachWithBus(v physics.Vector, s *Space, tree *Tree, bus event.Handler, offsets ...float64) error { + en := bus.GetCallerMap().GetEntity(s.CID) if t, ok := en.(attachSpace); ok { as := t.getAttachSpace() as.aSpace = &s @@ -48,7 +52,7 @@ func Attach(v physics.Vector, s *Space, tree *Tree, offsets ...float64) error { if as.tree == nil { as.tree = DefaultTree } - as.binding = event.Bind(event.DefaultBus, event.Enter, t, attachSpaceEnter) + as.binding = event.Bind(bus, event.Enter, t, attachSpaceEnter) if len(offsets) > 0 { as.offX = offsets[0] if len(offsets) > 1 { @@ -63,7 +67,11 @@ func Attach(v physics.Vector, s *Space, tree *Tree, offsets ...float64) error { // Detach removes the attachSpaceEnter binding from an entity composed with // AttachSpace func Detach(s *Space) error { - en := event.DefaultCallerMap.GetEntity(s.CID) + return DetachWithBus(s, event.DefaultBus) +} + +func DetachWithBus(s *Space, bus event.Handler) error { + en := bus.GetCallerMap().GetEntity(s.CID) if as, ok := en.(attachSpace); ok { as.getAttachSpace().binding.Unbind() return nil diff --git a/collision/attachSpace_test.go b/collision/attachSpace_test.go index d46fbca6..34eeb965 100644 --- a/collision/attachSpace_test.go +++ b/collision/attachSpace_test.go @@ -1,6 +1,7 @@ package collision import ( + "fmt" "testing" "time" @@ -12,23 +13,22 @@ type aspace struct { AttachSpace } -func (as *aspace) Init() event.CallerID { - return event.NextID(as) -} - func TestAttachSpace(t *testing.T) { Clear() + b := event.NewBus(event.NewCallerMap()) go func() { for { <-time.After(5 * time.Millisecond) - <-event.TriggerBack(event.Enter, nil) + <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) } }() - as := aspace{} + as := &aspace{} + cid := b.GetCallerMap().Register(as) v := physics.NewVector(0, 0) - s := NewSpace(100, 100, 10, 10, as.Init()) + s := NewSpace(100, 100, 10, 10, cid) Add(s) - err := Attach(v, s, nil, 4, 4) + fmt.Println(s.CID) + err := AttachWithBus(v, s, nil, b, 4, 4) if err != nil { t.Fatalf("attach failed: %v", err) } @@ -41,7 +41,7 @@ func TestAttachSpace(t *testing.T) { t.Fatalf("expected attached space to have y of 9, was %v", s.Y()) } - err = Detach(s) + err = DetachWithBus(s, b) if err != nil { t.Fatalf("detach failed: %v", err) } @@ -59,10 +59,10 @@ func TestAttachSpace(t *testing.T) { s = NewUnassignedSpace(0, 0, 1, 1) err = Attach(v, s, nil) if err == nil { - t.Fatalf("unassinged space attach should have failed: %v", err) + t.Fatalf("unassigned space attach should have failed: %v", err) } err = Detach(s) if err == nil { - t.Fatalf("unassinged space detach should have failed: %v", err) + t.Fatalf("unassigned space detach should have failed: %v", err) } } diff --git a/collision/onCollision.go b/collision/onCollision.go index 67337a73..8c82aacc 100644 --- a/collision/onCollision.go +++ b/collision/onCollision.go @@ -22,6 +22,10 @@ func (cp *Phase) getCollisionPhase() *Phase { return cp } +func (cp *Phase) CID() event.CallerID { + return cp.OnCollisionS.CID +} + type collisionPhase interface { getCollisionPhase() *Phase } @@ -31,13 +35,12 @@ type collisionPhase interface { // entities begin to collide or stop colliding with the space. // If tree is nil, it uses DefTree func PhaseCollision(s *Space, tree *Tree) error { - return PhaseCollisionWithBus(s, tree, event.DefaultBus, event.DefaultCallerMap) + return PhaseCollisionWithBus(s, tree, event.DefaultBus) } -// PhaseCollisionWithBus allows for a non-default bus and non-default entity mapping -// in a phase collision binding. -func PhaseCollisionWithBus(s *Space, tree *Tree, bus event.Handler, entities *event.CallerMap) error { - en := entities.GetEntity(s.CID) +// PhaseCollisionWithBus allows for a non-default bus in a phase collision binding. +func PhaseCollisionWithBus(s *Space, tree *Tree, bus event.Handler) error { + en := bus.GetCallerMap().GetEntity(s.CID) if cp, ok := en.(collisionPhase); ok { oc := cp.getCollisionPhase() oc.OnCollisionS = s diff --git a/collision/onCollision_test.go b/collision/onCollision_test.go index 6c3de14b..110036d5 100644 --- a/collision/onCollision_test.go +++ b/collision/onCollision_test.go @@ -12,54 +12,50 @@ type cphase struct { callers *event.CallerMap } -func (cp *cphase) Init() event.CallerID { - return cp.callers.NextID(cp) -} - func TestCollisionPhase(t *testing.T) { - callers := event.NewCallerMap() - bus := event.NewBus(callers) + b := event.NewBus(event.NewCallerMap()) go func() { for { <-time.After(5 * time.Millisecond) - <-bus.TriggerBack(event.Enter, nil) + <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) } }() - cp := cphase{ - callers: callers, - } - cid := cp.Init() + cp := &cphase{} + cid := b.GetCallerMap().Register(cp) s := NewSpace(10, 10, 10, 10, cid) tree := NewTree() - err := PhaseCollisionWithBus(s, tree, bus, callers) + err := PhaseCollisionWithBus(s, tree, b) if err != nil { t.Fatalf("phase collision failed: %v", err) } - var active bool - bus.Bind("CollisionStart", cid, func(event.CallerID, interface{}) int { - active = true + activeCh := make(chan bool, 5) + b1 := event.Bind(b, Start, cp, func(_ *cphase, _ Label) event.Response { + activeCh <- true return 0 }) - bus.Bind("CollisionStop", cid, func(event.CallerID, interface{}) int { - active = false + b2 := event.Bind(b, Stop, cp, func(_ *cphase, _ Label) event.Response { + activeCh <- false return 0 }) - + <-b1.Bound + <-b2.Bound s2 := NewLabeledSpace(15, 15, 10, 10, 5) tree.Add(s2) - time.Sleep(200 * time.Millisecond) - if !active { + if active := <-activeCh; !active { t.Fatalf("collision should be active") } tree.Remove(s2) time.Sleep(200 * time.Millisecond) - if active { + if active := <-activeCh; active { t.Fatalf("collision should be inactive") } +} +func TestPhaseCollision_Unembedded(t *testing.T) { + t.Parallel() s3 := NewSpace(10, 10, 10, 10, 5) - err = PhaseCollision(s3, nil) + err := PhaseCollision(s3, nil) if err == nil { t.Fatalf("phase collision should have failed") } From 82ac45749bc3003b405993c21fe8bd2a4e79f302 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 26 Mar 2022 17:46:56 -0500 Subject: [PATCH 14/41] scene: update tests for new event api --- scene/map_test.go | 3 --- scene/scene_test.go | 15 --------------- 2 files changed, 18 deletions(-) diff --git a/scene/map_test.go b/scene/map_test.go index 91e4c424..3678c1b0 100644 --- a/scene/map_test.go +++ b/scene/map_test.go @@ -77,9 +77,6 @@ func TestAddScene(t *testing.T) { t.Fatalf("getting test scene failed") } - if !test1.Loop() { - t.Fatalf("test loop failed") - } eStr, _ := test1.End() if eStr != "test1" { t.Fatalf("looping test end did not return test1, got %v", eStr) diff --git a/scene/scene_test.go b/scene/scene_test.go index 5d4946c7..683f240e 100644 --- a/scene/scene_test.go +++ b/scene/scene_test.go @@ -5,21 +5,6 @@ import ( "testing" ) -func TestBooleanLoop(t *testing.T) { - v := true - l := BooleanLoop(&v) - if !l() { - t.Fatalf("boolean loop should loop when true") - } - v = false - if l() { - t.Fatalf("boolean loop should not loop when false") - } - if !l() { - t.Fatalf("boolean loop should resume looping after returning false") - } -} - func randString() string { length := rand.Intn(100) data := make([]byte, length) From c97375964867700a8c334565686340ae22cba761 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 26 Mar 2022 17:52:41 -0500 Subject: [PATCH 15/41] render: correct tests for new event api --- render/logicfps_test.go | 4 +++- render/particle/allocator_test.go | 2 +- render/particle/source_test.go | 15 ++------------- render/reverting_test.go | 2 +- render/sequence_test.go | 23 ++++++----------------- 5 files changed, 13 insertions(+), 33 deletions(-) diff --git a/render/logicfps_test.go b/render/logicfps_test.go index c6c86e6a..62e908fa 100644 --- a/render/logicfps_test.go +++ b/render/logicfps_test.go @@ -3,12 +3,14 @@ package render import ( "image" "testing" + + "github.com/oakmound/oak/v3/event" ) func TestLogicFPS(t *testing.T) { lfps := NewLogicFPS(0, nil, 0, 0) lfps.Draw(image.NewRGBA(image.Rect(0, 0, 100, 100)), 10, 10) - logicFPSBind(lfps.CID, nil) + logicFPSBind(lfps, event.EnterPayload{}) if lfps.fps == 0 { t.Fatalf("fps not set by binding") } diff --git a/render/particle/allocator_test.go b/render/particle/allocator_test.go index 0e616c3a..31c73f59 100644 --- a/render/particle/allocator_test.go +++ b/render/particle/allocator_test.go @@ -33,7 +33,7 @@ func TestAllocatorLookup(t *testing.T) { go a.Run() src := NewSource(NewColorGenerator(), 0) - cid := src.CID + cid := src.CID() pidBlock := a.Allocate(cid) src2 := a.LookupSource(pidBlock * blockSize) if src != src2 { diff --git a/render/particle/source_test.go b/render/particle/source_test.go index c70c1f76..b9bb440f 100644 --- a/render/particle/source_test.go +++ b/render/particle/source_test.go @@ -48,9 +48,9 @@ func TestSource(t *testing.T) { } for i := 0; i < 1000; i++ { - rotateParticles(src.CID, nil) + rotateParticles(src, event.EnterPayload{}) } - for clearParticles(src.CID, nil) != event.UnbindEvent { + for clearParticles(src, event.EnterPayload{}) != event.UnbindThis { } if !ended { @@ -87,14 +87,3 @@ func TestSource(t *testing.T) { var src2 *Source src2.Stop() } - -func TestClearParticles(t *testing.T) { - t.Parallel() - t.Run("BadTypeBinding", func(t *testing.T) { - t.Parallel() - result := clearParticles(10000, nil) - if result != event.UnbindEvent { - t.Fatalf("expected UnbindEvent result, got %v", result) - } - }) -} diff --git a/render/reverting_test.go b/render/reverting_test.go index d4d324e1..49cae688 100644 --- a/render/reverting_test.go +++ b/render/reverting_test.go @@ -157,7 +157,7 @@ func TestRevertingCascadeFns(t *testing.T) { t.Fatalf("reverting unpause did not resume underlying sequence") } rv.SetTriggerID(1) - if sq.cID != 1 { + if sq.CallerID != 1 { t.Fatalf("reverting cID did not set underlying squence cID") } rv.update() diff --git a/render/sequence_test.go b/render/sequence_test.go index aaf13778..ceb16cc7 100644 --- a/render/sequence_test.go +++ b/render/sequence_test.go @@ -12,30 +12,19 @@ import ( "github.com/oakmound/oak/v3/render/mod" ) -type Dummy struct{} - -func (d Dummy) Init() event.CallerID { - return event.NextID(d) +type Dummy struct { + event.CallerID } func TestSequenceTrigger(t *testing.T) { sq := NewSequence(5, NewColorBox(10, 10, color.RGBA{255, 0, 0, 255}), NewColorBox(10, 10, color.RGBA{0, 255, 0, 255})) - cid := Dummy{}.Init() - sq.SetTriggerID(cid) + d := Dummy{} + d.CallerID = event.DefaultCallerMap.Register(d) + sq.SetTriggerID(d.CallerID) triggerCh := make(chan struct{}) - cid.Bind(event.AnimationEnd, func(event.CallerID, interface{}) int { - // This is a bad idea in real code, this will lock up - // unbindings because the function that triggered this owns - // the lock on the event bus until this function exits. - // It is for this reason that all triggers, bindings, - // and unbindings do nothing when they are called, just put - // off work to be done-- to make sure no one is expecting a - // result from one of those functions, from within a triggered - // function, causing a deadlock. - // - // For this test this is the easiest way to do this though + event.Bind(event.DefaultBus, AnimationEnd, d, func(_ Dummy, _ event.NoPayload) event.Response { triggerCh <- struct{}{} return 0 }) From daa0deded19f8d2fc7d333357946ce626805abe2 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 26 Mar 2022 18:02:50 -0500 Subject: [PATCH 16/41] mouse: correct tests for new event api --- mouse/event_test.go | 2 +- mouse/mouse_test.go | 20 +++++++-------- mouse/onCollision.go | 13 +++++----- mouse/onCollision_test.go | 51 +++++++++++++++++++++------------------ 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/mouse/event_test.go b/mouse/event_test.go index 145bfcd7..e77c2f37 100644 --- a/mouse/event_test.go +++ b/mouse/event_test.go @@ -7,7 +7,7 @@ import ( ) func TestEventConversions(t *testing.T) { - me := NewZeroEvent(1.0, 1.0) + me := NewEvent(1.0, 1.0, ButtonLeft, Drag) s := me.ToSpace() Add(collision.NewUnassignedSpace(1.0, 1.0, .1, .1)) if len(Hits(s)) == 0 { diff --git a/mouse/mouse_test.go b/mouse/mouse_test.go index 8d04a33e..ac0686d3 100644 --- a/mouse/mouse_test.go +++ b/mouse/mouse_test.go @@ -7,19 +7,19 @@ import ( ) func TestEventNameIdentity(t *testing.T) { - if GetEventName(mouse.DirPress, 0) != "MousePress" { - t.Fatalf("event name mismatch for event %v, expected %v", mouse.DirPress, "MousePress") + if GetEvent(mouse.DirPress, 0) != Press { + t.Fatalf("event mismatch for event %v, expected %v", mouse.DirPress, "MousePress") } - if GetEventName(mouse.DirRelease, 0) != "MouseRelease" { - t.Fatalf("event name mismatch for event %v, expected %v", mouse.DirRelease, "MouseRelease") + if GetEvent(mouse.DirRelease, 0) != Release { + t.Fatalf("event mismatch for event %v, expected %v", mouse.DirRelease, "MouseRelease") } - if GetEventName(mouse.DirNone, -2) != "MouseScrollDown" { - t.Fatalf("event name mismatch for event %v, expected %v", mouse.DirNone, "MouseScrollDown") + if GetEvent(mouse.DirNone, -2) != ScrollDown { + t.Fatalf("event mismatch for event %v, expected %v", mouse.DirNone, "MouseScrollDown") } - if GetEventName(mouse.DirNone, -1) != "MouseScrollUp" { - t.Fatalf("event name mismatch for event %v, expected %v", mouse.DirNone, "MouseScrollUp") + if GetEvent(mouse.DirNone, -1) != ScrollUp { + t.Fatalf("event mismatch for event %v, expected %v", mouse.DirNone, "MouseScrollUp") } - if GetEventName(mouse.DirNone, 0) != "MouseDrag" { - t.Fatalf("event name mismatch for event %v, expected %v", mouse.DirNone, "MouseDrag") + if GetEvent(mouse.DirNone, 0) != Drag { + t.Fatalf("event mismatch for event %v, expected %v", mouse.DirNone, "MouseDrag") } } diff --git a/mouse/onCollision.go b/mouse/onCollision.go index be5803e1..1190a1b3 100644 --- a/mouse/onCollision.go +++ b/mouse/onCollision.go @@ -21,6 +21,10 @@ func (cp *CollisionPhase) getCollisionPhase() *CollisionPhase { return cp } +func (cp *CollisionPhase) CID() event.CallerID { + return cp.OnCollisionS.CID +} + type collisionPhase interface { getCollisionPhase() *CollisionPhase } @@ -28,15 +32,12 @@ type collisionPhase interface { // PhaseCollision binds to the entity behind the space's CID so that it will // receive MouseCollisionStart and MouseCollisionStop events, appropriately when // the mouse begins to hover or stops hovering over the input space. -func PhaseCollision(s *collision.Space, callerMap *event.CallerMap, handler event.Handler) error { - if callerMap == nil { - callerMap = event.DefaultCallerMap - } - en := callerMap.GetEntity(s.CID) +func PhaseCollision(s *collision.Space, handler event.Handler) error { + en := handler.GetCallerMap().GetEntity(s.CID) if cp, ok := en.(collisionPhase); ok { oc := cp.getCollisionPhase() oc.OnCollisionS = s - oc.CallerMap = callerMap + oc.CallerMap = handler.GetCallerMap() handler.UnsafeBind(event.Enter.UnsafeEventID, s.CID, phaseCollisionEnter) return nil } diff --git a/mouse/onCollision_test.go b/mouse/onCollision_test.go index 5afedb90..12fe61a5 100644 --- a/mouse/onCollision_test.go +++ b/mouse/onCollision_test.go @@ -11,51 +11,56 @@ import ( type cphase struct { CollisionPhase -} - -func (cp *cphase) Init() event.CallerID { - return event.NextID(cp) + callers *event.CallerMap } func TestCollisionPhase(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) go func() { for { <-time.After(5 * time.Millisecond) - <-event.TriggerBack(event.Enter, nil) + <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) } }() - cp := cphase{} - cid := cp.Init() + cp := &cphase{} + cid := b.GetCallerMap().Register(cp) s := collision.NewSpace(10, 10, 10, 10, cid) - if PhaseCollision(s) != nil { - t.Fatalf("phase collision errored") + err := PhaseCollision(s, b) + if err != nil { + t.Fatalf("phase collision failed: %v", err) } - var active bool - cid.Bind("MouseCollisionStart", func(event.CallerID, interface{}) int { - active = true + activeCh := make(chan bool, 5) + b1 := event.Bind(b, Start, cp, func(_ *cphase, _ *Event) event.Response { + activeCh <- true return 0 }) - cid.Bind("MouseCollisionStop", func(event.CallerID, interface{}) int { - active = false + b2 := event.Bind(b, Stop, cp, func(_ *cphase, _ *Event) event.Response { + activeCh <- false return 0 }) - time.Sleep(200 * time.Millisecond) + <-b1.Bound + <-b2.Bound LastEvent = Event{ Point2: floatgeom.Point2{10, 10}, } - time.Sleep(200 * time.Millisecond) - if !active { - t.Fatalf("phase collision did not trigger") + if active := <-activeCh; !active { + t.Fatalf("collision should be active") } + LastEvent = Event{ Point2: floatgeom.Point2{21, 21}, } time.Sleep(200 * time.Millisecond) - if active { - t.Fatalf("phase collision triggered innapropriately") + if active := <-activeCh; active { + t.Fatalf("collision should be inactive") } - s = collision.NewSpace(10, 10, 10, 10, 5) - if PhaseCollision(s) == nil { - t.Fatalf("phase collision did not error on invalid space") +} + +func TestPhaseCollision_Unembedded(t *testing.T) { + t.Parallel() + s3 := collision.NewSpace(10, 10, 10, 10, 5) + err := PhaseCollision(s3, event.DefaultBus) + if err == nil { + t.Fatalf("phase collision should have failed") } } From f983f31a529dca5a3ceaf8ab0cbd725cd5f7c3da Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 26 Mar 2022 19:06:52 -0400 Subject: [PATCH 17/41] scene: Embed Eventhandler to allow for some embedded calls --- debugtools/inputviz/joystick.go | 10 +++++----- debugtools/inputviz/keyboard.go | 4 ++-- debugtools/inputviz/mouse.go | 2 +- debugtools/mouse.go | 2 +- examples/bezier/main.go | 2 +- examples/click-propagation/main.go | 10 +++++----- examples/collision-demo/main.go | 6 +++--- examples/custom-cursor/main.go | 2 +- examples/joystick-viz/main.go | 2 +- examples/multi-window/main.go | 4 ++-- examples/pong/main.go | 6 +++--- scene/context.go | 9 +++++---- sceneLoop.go | 2 +- 13 files changed, 31 insertions(+), 30 deletions(-) diff --git a/debugtools/inputviz/joystick.go b/debugtools/inputviz/joystick.go index f8fea3b3..1b9bf7ba 100644 --- a/debugtools/inputviz/joystick.go +++ b/debugtools/inputviz/joystick.go @@ -183,13 +183,13 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l joystick.InputRightShoulder, } - b1 := event.Bind(ctx.EventHandler, joystick.Disconnected, j, func(rend *Joystick, _ uint32) event.Response { + b1 := event.Bind(ctx.Handler, joystick.Disconnected, j, func(rend *Joystick, _ uint32) event.Response { j.Destroy() return 0 }) // TODO: it is bad that you need to import two 'key' packages - b2 := event.Bind(ctx.EventHandler, key.Down(mkey.CodeSpacebar), j, func(j *Joystick, _ key.Event) event.Response { + b2 := event.Bind(ctx.Handler, key.Down(mkey.CodeSpacebar), j, func(j *Joystick, _ key.Event) event.Response { j.joy.Vibrate(math.MaxUint16, math.MaxUint16) go func() { time.Sleep(1 * time.Second) @@ -198,7 +198,7 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l return 0 }) - b3 := event.Bind(ctx.EventHandler, joystick.Change, j, func(j *Joystick, st *joystick.State) event.Response { + b3 := event.Bind(ctx.Handler, joystick.Change, j, func(j *Joystick, st *joystick.State) event.Response { for _, inputB := range bts { b := string(inputB) r := j.rs[b] @@ -220,7 +220,7 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l return 0 }) - b4 := event.Bind(ctx.EventHandler, joystick.LtStickChange, j, func(j *Joystick, st *joystick.State) event.Response { + b4 := event.Bind(ctx.Handler, joystick.LtStickChange, j, func(j *Joystick, st *joystick.State) event.Response { pos := j.lStickCenter pos = pos.Add(floatgeom.Point2{ float64(st.StickLX / 2048), @@ -230,7 +230,7 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l return 0 }) - b5 := event.Bind(ctx.EventHandler, joystick.RtStickChange, j, func(j *Joystick, st *joystick.State) event.Response { + b5 := event.Bind(ctx.Handler, joystick.RtStickChange, j, func(j *Joystick, st *joystick.State) event.Response { pos := j.rStickCenter pos = pos.Add(floatgeom.Point2{ float64(st.StickRX / 2048), diff --git a/debugtools/inputviz/keyboard.go b/debugtools/inputviz/keyboard.go index 4a905ac1..bccc0407 100644 --- a/debugtools/inputviz/keyboard.go +++ b/debugtools/inputviz/keyboard.go @@ -221,7 +221,7 @@ func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { } } - b1 := event.Bind(ctx.EventHandler, key.AnyDown, k, func(kb *Keyboard, ev key.Event) event.Response { + b1 := event.Bind(ctx.Handler, key.AnyDown, k, func(kb *Keyboard, ev key.Event) event.Response { btn := ev.Code.String()[4:] if kb.rs[btn] == nil { return 0 @@ -229,7 +229,7 @@ func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { kb.rs[btn].Set("pressed") return 0 }) - b2 := event.Bind(ctx.EventHandler, key.AnyUp, k, func(kb *Keyboard, ev key.Event) event.Response { + b2 := event.Bind(ctx.Handler, key.AnyUp, k, func(kb *Keyboard, ev key.Event) event.Response { btn := ev.Code.String()[4:] if kb.rs[btn] == nil { return 0 diff --git a/debugtools/inputviz/mouse.go b/debugtools/inputviz/mouse.go index c07fcf2b..c64f4df7 100644 --- a/debugtools/inputviz/mouse.go +++ b/debugtools/inputviz/mouse.go @@ -37,7 +37,7 @@ func (m *Mouse) CID() event.CallerID { func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { m.ctx = ctx - handler := ctx.EventHandler + handler := ctx.Handler m.CallerID = handler.GetCallerMap().Register(m) if m.Rect.W() == 0 || m.Rect.H() == 0 { diff --git a/debugtools/mouse.go b/debugtools/mouse.go index 7cd74b66..adc7752d 100644 --- a/debugtools/mouse.go +++ b/debugtools/mouse.go @@ -10,7 +10,7 @@ import ( // DebugMouseRelease will print the position and button pressed of the mouse when the mouse is released, if the given // key is held down at the time. If no key is given, it will always be printed func DebugMouseRelease(ctx *scene.Context, k string) { - event.GlobalBind(ctx.EventHandler, mouse.Release, func(mev *mouse.Event) event.Response { + event.GlobalBind(ctx.Handler, mouse.Release, func(mev *mouse.Event) event.Response { if k == "" || ctx.KeyState.IsDown(k) { dlog.Info(mev) } diff --git a/examples/bezier/main.go b/examples/bezier/main.go index 92a65afe..cfd6a905 100644 --- a/examples/bezier/main.go +++ b/examples/bezier/main.go @@ -55,7 +55,7 @@ func main() { oak.AddScene("bezier", scene.Scene{Start: func(ctx *scene.Context) { mouseFloats := []float64{} - event.GlobalBind(ctx.EventHandler, + event.GlobalBind(ctx.Handler, mouse.Press, func(me *mouse.Event) event.Response { // Left click to add a point to the curve if me.Button == mouse.ButtonLeft { diff --git a/examples/click-propagation/main.go b/examples/click-propagation/main.go index 2cf97753..022abf70 100644 --- a/examples/click-propagation/main.go +++ b/examples/click-propagation/main.go @@ -46,30 +46,30 @@ func (hb *hoverButton) CID() event.CallerID { func newHoverButton(ctx *scene.Context, x, y, w, h float64, clr color.RGBA, layer int) { hb := &hoverButton{} - hb.id = ctx.CallerMap.Register(hb) + hb.id = ctx.Register(hb) hb.changingColorBox = newChangingColorBox(x, y, int(w), int(h), clr) sp := collision.NewSpace(x, y, w, h, hb.id) sp.SetZLayer(float64(layer)) mouse.Add(sp) - mouse.PhaseCollision(sp, ctx.EventHandler.GetCallerMap(), ctx.EventHandler) + mouse.PhaseCollision(sp, ctx.GetCallerMap(), ctx.Handler) render.Draw(hb.changingColorBox, 0, layer) - event.Bind(ctx.EventHandler, mouse.Click, hb, func(box *hoverButton, me *mouse.Event) event.Response { + event.Bind(ctx.Handler, mouse.Click, hb, func(box *hoverButton, me *mouse.Event) event.Response { fmt.Println(box, me.Point2) box.changingColorBox.c = color.RGBA{128, 128, 128, 128} me.StopPropagation = true return 0 }) - event.Bind(ctx.EventHandler, mouse.Start, hb, func(box *hoverButton, me *mouse.Event) event.Response { + event.Bind(ctx.Handler, mouse.Start, hb, func(box *hoverButton, me *mouse.Event) event.Response { fmt.Println("start") box.changingColorBox.c = color.RGBA{50, 50, 50, 50} me.StopPropagation = true return 0 }) - event.Bind(ctx.EventHandler, mouse.Stop, hb, func(box *hoverButton, me *mouse.Event) event.Response { + event.Bind(ctx.Handler, mouse.Stop, hb, func(box *hoverButton, me *mouse.Event) event.Response { fmt.Println("stop") box.changingColorBox.c = clr me.StopPropagation = true diff --git a/examples/collision-demo/main.go b/examples/collision-demo/main.go index 4d32722e..a3c9528e 100644 --- a/examples/collision-demo/main.go +++ b/examples/collision-demo/main.go @@ -26,7 +26,7 @@ func main() { collision.Attach(act.Vector, act.Space, nil, 0, 0) - event.Bind(ctx.EventHandler, event.Enter, act, func(act *AttachCollisionTest, ev event.EnterPayload) event.Response { + event.Bind(ctx.Handler, event.Enter, act, func(act *AttachCollisionTest, ev event.EnterPayload) event.Response { if act.ShouldUpdate { act.ShouldUpdate = false act.R.Undraw() @@ -57,7 +57,7 @@ func main() { collision.PhaseCollision(act.Space, nil) - event.Bind(ctx.EventHandler, collision.Start, act, func(act *AttachCollisionTest, l collision.Label) event.Response { + event.Bind(ctx.Handler, collision.Start, act, func(act *AttachCollisionTest, l collision.Label) event.Response { switch l { case RED: act.r += 125 @@ -75,7 +75,7 @@ func main() { } return 0 }) - event.Bind(ctx.EventHandler, collision.Stop, act, func(act *AttachCollisionTest, l collision.Label) event.Response { + event.Bind(ctx.Handler, collision.Stop, act, func(act *AttachCollisionTest, l collision.Label) event.Response { switch l { case RED: act.r -= 125 diff --git a/examples/custom-cursor/main.go b/examples/custom-cursor/main.go index e46e292d..898fbed4 100644 --- a/examples/custom-cursor/main.go +++ b/examples/custom-cursor/main.go @@ -37,7 +37,7 @@ func main() { ) ctx.DrawStack.Draw(box) - ctx.EventHandler.GlobalBind(mouse.Drag, func(_ event.CallerID, me interface{}) int { + ctx.Handler.GlobalBind(mouse.Drag, func(_ event.CallerID, me interface{}) int { mouseEvent := me.(*mouse.Event) box.SetPos(mouseEvent.X(), mouseEvent.Y()) return 0 diff --git a/examples/joystick-viz/main.go b/examples/joystick-viz/main.go index a0a1c40a..6980aa92 100644 --- a/examples/joystick-viz/main.go +++ b/examples/joystick-viz/main.go @@ -22,7 +22,7 @@ func main() { *latestInput = "Latest Input: Keyboard+Mouse" ctx.DrawStack.Draw(render.NewStrPtrText(latestInput, 10, 460), 4) ctx.DrawStack.Draw(render.NewText("Space to Vibrate", 10, 440), 4) - ctx.EventHandler.GlobalBind(event.InputChange, func(_ event.CallerID, payload interface{}) int { + ctx.Handler.GlobalBind(event.InputChange, func(_ event.CallerID, payload interface{}) int { input := payload.(oak.InputType) switch input { case oak.InputJoystick: diff --git a/examples/multi-window/main.go b/examples/multi-window/main.go index 28e123f8..4407c8cd 100644 --- a/examples/multi-window/main.go +++ b/examples/multi-window/main.go @@ -26,7 +26,7 @@ func main() { ctx.DrawStack.Draw(cb, 0) dFPS := render.NewDrawFPS(0.1, nil, 600, 10) ctx.DrawStack.Draw(dFPS, 1) - ctx.EventHandler.GlobalBind(mouse.Press, mouse.Binding(func(_ event.CallerID, me *mouse.Event) int { + ctx.Handler.GlobalBind(mouse.Press, mouse.Binding(func(_ event.CallerID, me *mouse.Event) int { cb.SetPos(me.X(), me.Y()) return 0 })) @@ -55,7 +55,7 @@ func main() { ctx.DrawStack.Draw(cb, 0) dFPS := render.NewDrawFPS(0.1, nil, 600, 10) ctx.DrawStack.Draw(dFPS, 1) - ctx.EventHandler.GlobalBind(mouse.Press, mouse.Binding(func(_ event.CallerID, me *mouse.Event) int { + ctx.Handler.GlobalBind(mouse.Press, mouse.Binding(func(_ event.CallerID, me *mouse.Event) int { cb.SetPos(me.X(), me.Y()) return 0 })) diff --git a/examples/pong/main.go b/examples/pong/main.go index 24dea2a3..9a8d577a 100644 --- a/examples/pong/main.go +++ b/examples/pong/main.go @@ -41,7 +41,7 @@ func main() { func newBall(ctx *scene.Context, x, y float64) { b := entities.NewMoving(x, y, 10, 10, render.NewColorBoxR(10, 10, color.RGBA{255, 255, 255, 255}), nil, 0, 0) render.Draw(b.R, 2) - event.GlobalBind(ctx.EventHandler, event.Enter, func(_ event.EnterPayload) event.Response { + event.GlobalBind(ctx.Handler, event.Enter, func(_ event.EnterPayload) event.Response { if b.Delta.X() == 0 && b.Delta.Y() == 0 { b.Delta.SetY((rand.Float64() - 0.5) * 4) b.Delta.SetX((rand.Float64() - 0.5) * 16) @@ -76,9 +76,9 @@ func newPaddle(ctx *scene.Context, x, y float64, player int) { render.Draw(p.R, 1) p.Space.UpdateLabel(hitPaddle) if player == 1 { - event.Bind(ctx.EventHandler, event.Enter, p, enterPaddle(key.UpArrow, key.DownArrow)) + event.Bind(ctx.Handler, event.Enter, p, enterPaddle(key.UpArrow, key.DownArrow)) } else { - event.Bind(ctx.EventHandler, event.Enter, p, enterPaddle(key.W, key.S)) + event.Bind(ctx.Handler, event.Enter, p, enterPaddle(key.W, key.S)) } } diff --git a/scene/context.go b/scene/context.go index 4121b2b9..79e28aee 100644 --- a/scene/context.go +++ b/scene/context.go @@ -15,18 +15,19 @@ import ( // and a reference to the OS window itself. When a scene ends, modifications made // to these structures will be reset, excluding window modifications. // TODO oak v4: consider embedding these system objects on the context to change -// ctx.DrawStack.Draw to ctx.Draw and ctx.EventHandler.Bind to ctx.Bind +// ctx.DrawStack.Draw to ctx.Draw and ctx.Handler.Bind to ctx.Bind type Context struct { // This context will be canceled when the scene ends context.Context + *event.CallerMap + event.Handler PreviousScene string SceneInput interface{} Window window.Window - DrawStack *render.DrawStack - EventHandler event.Handler - CallerMap *event.CallerMap + DrawStack *render.DrawStack + MouseTree *collision.Tree CollisionTree *collision.Tree KeyState *key.State diff --git a/sceneLoop.go b/sceneLoop.go index 5b07452f..096199b1 100644 --- a/sceneLoop.go +++ b/sceneLoop.go @@ -74,7 +74,7 @@ func (w *Window) sceneLoop(first string, trackingInputs, batchLoad bool) { PreviousScene: prevScene, SceneInput: result.NextSceneInput, DrawStack: w.DrawStack, - EventHandler: w.eventHandler, + Handler: w.eventHandler, CallerMap: w.CallerMap, MouseTree: w.MouseTree, CollisionTree: w.CollisionTree, From 66021490f490d32baa5a339ab5991c1b1dab303b Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 26 Mar 2022 18:20:26 -0500 Subject: [PATCH 18/41] event: rename CallerMap.Reset to Clear, enabling embedding via scene.Context --- debugtools/inputviz/joystick.go | 10 +++++----- debugtools/inputviz/keyboard.go | 4 ++-- debugtools/inputviz/mouse.go | 13 ++++++------- debugtools/mouse.go | 2 +- event/caller.go | 4 ++-- examples/bezier/main.go | 2 +- examples/click-propagation/main.go | 6 +++--- examples/collision-demo/main.go | 6 +++--- examples/pong/main.go | 6 +++--- sceneLoop.go | 2 +- 10 files changed, 27 insertions(+), 28 deletions(-) diff --git a/debugtools/inputviz/joystick.go b/debugtools/inputviz/joystick.go index 1b9bf7ba..c2fcb46b 100644 --- a/debugtools/inputviz/joystick.go +++ b/debugtools/inputviz/joystick.go @@ -183,13 +183,13 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l joystick.InputRightShoulder, } - b1 := event.Bind(ctx.Handler, joystick.Disconnected, j, func(rend *Joystick, _ uint32) event.Response { + b1 := event.Bind(ctx, joystick.Disconnected, j, func(rend *Joystick, _ uint32) event.Response { j.Destroy() return 0 }) // TODO: it is bad that you need to import two 'key' packages - b2 := event.Bind(ctx.Handler, key.Down(mkey.CodeSpacebar), j, func(j *Joystick, _ key.Event) event.Response { + b2 := event.Bind(ctx, key.Down(mkey.CodeSpacebar), j, func(j *Joystick, _ key.Event) event.Response { j.joy.Vibrate(math.MaxUint16, math.MaxUint16) go func() { time.Sleep(1 * time.Second) @@ -198,7 +198,7 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l return 0 }) - b3 := event.Bind(ctx.Handler, joystick.Change, j, func(j *Joystick, st *joystick.State) event.Response { + b3 := event.Bind(ctx, joystick.Change, j, func(j *Joystick, st *joystick.State) event.Response { for _, inputB := range bts { b := string(inputB) r := j.rs[b] @@ -220,7 +220,7 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l return 0 }) - b4 := event.Bind(ctx.Handler, joystick.LtStickChange, j, func(j *Joystick, st *joystick.State) event.Response { + b4 := event.Bind(ctx, joystick.LtStickChange, j, func(j *Joystick, st *joystick.State) event.Response { pos := j.lStickCenter pos = pos.Add(floatgeom.Point2{ float64(st.StickLX / 2048), @@ -230,7 +230,7 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l return 0 }) - b5 := event.Bind(ctx.Handler, joystick.RtStickChange, j, func(j *Joystick, st *joystick.State) event.Response { + b5 := event.Bind(ctx, joystick.RtStickChange, j, func(j *Joystick, st *joystick.State) event.Response { pos := j.rStickCenter pos = pos.Add(floatgeom.Point2{ float64(st.StickRX / 2048), diff --git a/debugtools/inputviz/keyboard.go b/debugtools/inputviz/keyboard.go index bccc0407..52b24f46 100644 --- a/debugtools/inputviz/keyboard.go +++ b/debugtools/inputviz/keyboard.go @@ -221,7 +221,7 @@ func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { } } - b1 := event.Bind(ctx.Handler, key.AnyDown, k, func(kb *Keyboard, ev key.Event) event.Response { + b1 := event.Bind(ctx, key.AnyDown, k, func(kb *Keyboard, ev key.Event) event.Response { btn := ev.Code.String()[4:] if kb.rs[btn] == nil { return 0 @@ -229,7 +229,7 @@ func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { kb.rs[btn].Set("pressed") return 0 }) - b2 := event.Bind(ctx.Handler, key.AnyUp, k, func(kb *Keyboard, ev key.Event) event.Response { + b2 := event.Bind(ctx, key.AnyUp, k, func(kb *Keyboard, ev key.Event) event.Response { btn := ev.Code.String()[4:] if kb.rs[btn] == nil { return 0 diff --git a/debugtools/inputviz/mouse.go b/debugtools/inputviz/mouse.go index c64f4df7..b0d2323c 100644 --- a/debugtools/inputviz/mouse.go +++ b/debugtools/inputviz/mouse.go @@ -37,8 +37,7 @@ func (m *Mouse) CID() event.CallerID { func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { m.ctx = ctx - handler := ctx.Handler - m.CallerID = handler.GetCallerMap().Register(m) + m.CallerID = ctx.Handler.GetCallerMap().Register(m) if m.Rect.W() == 0 || m.Rect.H() == 0 { m.Rect.Max = m.Rect.Min.Add(floatgeom.Point2{60, 100}) @@ -100,21 +99,21 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { ctx.DrawStack.Draw(m.posText, m.BaseLayer, layer+2) } - b1 := event.Bind(handler, mouse.Press, m, func(m *Mouse, ev *mouse.Event) event.Response { + b1 := event.Bind(ctx, mouse.Press, m, func(m *Mouse, ev *mouse.Event) event.Response { m.rs[ev.Button].Set("pressed") m.stateIncLock.Lock() m.stateInc[ev.Button]++ m.stateIncLock.Unlock() return 0 }) - b2 := event.Bind(handler, mouse.Release, m, func(m *Mouse, ev *mouse.Event) event.Response { + b2 := event.Bind(ctx, mouse.Release, m, func(m *Mouse, ev *mouse.Event) event.Response { m.rs[ev.Button].Set("released") m.stateIncLock.Lock() m.stateInc[ev.Button]++ m.stateIncLock.Unlock() return 0 }) - b3 := event.Bind(handler, mouse.ScrollDown, m, func(m *Mouse, ev *mouse.Event) event.Response { + b3 := event.Bind(ctx, mouse.ScrollDown, m, func(m *Mouse, ev *mouse.Event) event.Response { m.rs[mouse.ButtonMiddle].Set("scrolldown") m.stateIncLock.Lock() m.stateInc[mouse.ButtonMiddle]++ @@ -129,7 +128,7 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { }) return 0 }) - b4 := event.Bind(handler, mouse.ScrollUp, m, func(m *Mouse, ev *mouse.Event) event.Response { + b4 := event.Bind(ctx, mouse.ScrollUp, m, func(m *Mouse, ev *mouse.Event) event.Response { m.rs[mouse.ButtonMiddle].Set("scrollup") m.stateIncLock.Lock() m.stateInc[mouse.ButtonMiddle]++ @@ -144,7 +143,7 @@ func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { }) return 0 }) - b5 := event.Bind(handler, mouse.Drag, m, func(m *Mouse, ev *mouse.Event) event.Response { + b5 := event.Bind(ctx, mouse.Drag, m, func(m *Mouse, ev *mouse.Event) event.Response { m.lastMousePos.Point2 = ev.Point2 return 0 }) diff --git a/debugtools/mouse.go b/debugtools/mouse.go index adc7752d..5b692c7f 100644 --- a/debugtools/mouse.go +++ b/debugtools/mouse.go @@ -10,7 +10,7 @@ import ( // DebugMouseRelease will print the position and button pressed of the mouse when the mouse is released, if the given // key is held down at the time. If no key is given, it will always be printed func DebugMouseRelease(ctx *scene.Context, k string) { - event.GlobalBind(ctx.Handler, mouse.Release, func(mev *mouse.Event) event.Response { + event.GlobalBind(ctx, mouse.Release, func(mev *mouse.Event) event.Response { if k == "" || ctx.KeyState.IsDown(k) { dlog.Info(mev) } diff --git a/event/caller.go b/event/caller.go index 09ca69bd..b9b9bc31 100644 --- a/event/caller.go +++ b/event/caller.go @@ -72,8 +72,8 @@ func (cm *CallerMap) DestroyEntity(id CallerID) { cm.callersLock.Unlock() } -// Reset clears the caller map to forget all registered callers. -func (cm *CallerMap) Reset() { +// Clear clears the caller map to forget all registered callers. +func (cm *CallerMap) Clear() { cm.callersLock.Lock() *cm.highestID = 0 cm.callers = map[CallerID]Caller{} diff --git a/examples/bezier/main.go b/examples/bezier/main.go index cfd6a905..eca12490 100644 --- a/examples/bezier/main.go +++ b/examples/bezier/main.go @@ -55,7 +55,7 @@ func main() { oak.AddScene("bezier", scene.Scene{Start: func(ctx *scene.Context) { mouseFloats := []float64{} - event.GlobalBind(ctx.Handler, + event.GlobalBind(ctx, mouse.Press, func(me *mouse.Event) event.Response { // Left click to add a point to the curve if me.Button == mouse.ButtonLeft { diff --git a/examples/click-propagation/main.go b/examples/click-propagation/main.go index 022abf70..72f591c1 100644 --- a/examples/click-propagation/main.go +++ b/examples/click-propagation/main.go @@ -57,19 +57,19 @@ func newHoverButton(ctx *scene.Context, x, y, w, h float64, clr color.RGBA, laye render.Draw(hb.changingColorBox, 0, layer) - event.Bind(ctx.Handler, mouse.Click, hb, func(box *hoverButton, me *mouse.Event) event.Response { + event.Bind(ctx, mouse.Click, hb, func(box *hoverButton, me *mouse.Event) event.Response { fmt.Println(box, me.Point2) box.changingColorBox.c = color.RGBA{128, 128, 128, 128} me.StopPropagation = true return 0 }) - event.Bind(ctx.Handler, mouse.Start, hb, func(box *hoverButton, me *mouse.Event) event.Response { + event.Bind(ctx, mouse.Start, hb, func(box *hoverButton, me *mouse.Event) event.Response { fmt.Println("start") box.changingColorBox.c = color.RGBA{50, 50, 50, 50} me.StopPropagation = true return 0 }) - event.Bind(ctx.Handler, mouse.Stop, hb, func(box *hoverButton, me *mouse.Event) event.Response { + event.Bind(ctx, mouse.Stop, hb, func(box *hoverButton, me *mouse.Event) event.Response { fmt.Println("stop") box.changingColorBox.c = clr me.StopPropagation = true diff --git a/examples/collision-demo/main.go b/examples/collision-demo/main.go index a3c9528e..571727a3 100644 --- a/examples/collision-demo/main.go +++ b/examples/collision-demo/main.go @@ -26,7 +26,7 @@ func main() { collision.Attach(act.Vector, act.Space, nil, 0, 0) - event.Bind(ctx.Handler, event.Enter, act, func(act *AttachCollisionTest, ev event.EnterPayload) event.Response { + event.Bind(ctx, event.Enter, act, func(act *AttachCollisionTest, ev event.EnterPayload) event.Response { if act.ShouldUpdate { act.ShouldUpdate = false act.R.Undraw() @@ -57,7 +57,7 @@ func main() { collision.PhaseCollision(act.Space, nil) - event.Bind(ctx.Handler, collision.Start, act, func(act *AttachCollisionTest, l collision.Label) event.Response { + event.Bind(ctx, collision.Start, act, func(act *AttachCollisionTest, l collision.Label) event.Response { switch l { case RED: act.r += 125 @@ -75,7 +75,7 @@ func main() { } return 0 }) - event.Bind(ctx.Handler, collision.Stop, act, func(act *AttachCollisionTest, l collision.Label) event.Response { + event.Bind(ctx, collision.Stop, act, func(act *AttachCollisionTest, l collision.Label) event.Response { switch l { case RED: act.r -= 125 diff --git a/examples/pong/main.go b/examples/pong/main.go index 9a8d577a..3dba7009 100644 --- a/examples/pong/main.go +++ b/examples/pong/main.go @@ -41,7 +41,7 @@ func main() { func newBall(ctx *scene.Context, x, y float64) { b := entities.NewMoving(x, y, 10, 10, render.NewColorBoxR(10, 10, color.RGBA{255, 255, 255, 255}), nil, 0, 0) render.Draw(b.R, 2) - event.GlobalBind(ctx.Handler, event.Enter, func(_ event.EnterPayload) event.Response { + event.GlobalBind(ctx, event.Enter, func(_ event.EnterPayload) event.Response { if b.Delta.X() == 0 && b.Delta.Y() == 0 { b.Delta.SetY((rand.Float64() - 0.5) * 4) b.Delta.SetX((rand.Float64() - 0.5) * 16) @@ -76,9 +76,9 @@ func newPaddle(ctx *scene.Context, x, y float64, player int) { render.Draw(p.R, 1) p.Space.UpdateLabel(hitPaddle) if player == 1 { - event.Bind(ctx.Handler, event.Enter, p, enterPaddle(key.UpArrow, key.DownArrow)) + event.Bind(ctx, event.Enter, p, enterPaddle(key.UpArrow, key.DownArrow)) } else { - event.Bind(ctx.Handler, event.Enter, p, enterPaddle(key.W, key.S)) + event.Bind(ctx, event.Enter, p, enterPaddle(key.W, key.S)) } } diff --git a/sceneLoop.go b/sceneLoop.go index 096199b1..359d17cb 100644 --- a/sceneLoop.go +++ b/sceneLoop.go @@ -129,7 +129,7 @@ func (w *Window) sceneLoop(first string, trackingInputs, batchLoad bool) { // be triggered and attempt to access an entity w.CollisionTree.Clear() w.MouseTree.Clear() - w.CallerMap.Reset() + w.CallerMap.Clear() w.eventHandler.SetCallerMap(w.CallerMap) w.DrawStack.Clear() w.DrawStack.PreDraw() From 747702d54661d7a0a038cd741966c6d57df74dca Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Mon, 28 Mar 2022 17:23:31 -0500 Subject: [PATCH 19/41] entities/x: adjust to compile with event api v4 --- entities/x/btn/box.go | 18 +++-- entities/x/btn/button.go | 121 +++++++++++++++-------------- entities/x/btn/option.go | 9 ++- entities/x/btn/textBox.go | 12 +-- entities/x/force/directionSpace.go | 9 ++- entities/x/stat/default.go | 26 ++++--- entities/x/stat/statistic.go | 8 +- entities/x/stat/stats.go | 60 +++++++++----- 8 files changed, 142 insertions(+), 121 deletions(-) diff --git a/entities/x/btn/box.go b/entities/x/btn/box.go index 0ddab483..b5a3606b 100644 --- a/entities/x/btn/box.go +++ b/entities/x/btn/box.go @@ -14,10 +14,17 @@ type Box struct { metadata map[string]string } +func (b Box) CID() event.CallerID { + return b.Solid.CID() +} + // NewBox creates a new Box func NewBox(cid event.CallerID, x, y, w, h float64, r render.Renderable, layers ...int) *Box { b := Box{} - cid = cid.Parse(&b) + if cid == 0 { + // TODO: not default + cid = event.DefaultCallerMap.Register(b) + } b.Solid = *entities.NewSolid(x, y, w, h, r, mouse.DefaultTree, cid) if b.R != nil && len(layers) > 0 { render.Draw(b.R, layers...) @@ -26,12 +33,6 @@ func NewBox(cid event.CallerID, x, y, w, h float64, r render.Renderable, layers return &b } -// Init intializes the Box -func (b *Box) Init() event.CallerID { - b.CID = event.NextID(b) - return b.CID -} - // GetRenderable returns the box's renderable func (b *Box) GetRenderable() render.Renderable { return b.R @@ -54,7 +55,8 @@ func (b *Box) Metadata(k string) (v string, ok bool) { } func (b *Box) Destroy() { - b.UnbindAll() + // TODO: not default + event.DefaultBus.UnbindAllFrom(b.CallerID) b.R.Undraw() mouse.Remove(b.GetSpace()) } diff --git a/entities/x/btn/button.go b/entities/x/btn/button.go index 9fbffb8a..00407564 100644 --- a/entities/x/btn/button.go +++ b/entities/x/btn/button.go @@ -4,7 +4,6 @@ import ( "fmt" "image/color" "strconv" - "strings" "github.com/oakmound/oak/v3/collision" "github.com/oakmound/oak/v3/dlog" @@ -35,7 +34,7 @@ type Generator struct { TextPtr *string TextStringer fmt.Stringer Children []Generator - Bindings map[string][]event.Bindable + Bindings []func(caller Btn) event.Binding Trigger string Toggle *bool ListChoice *int @@ -66,7 +65,6 @@ func defGenerator() Generator { Font: nil, Layers: []int{0}, Text: "Button", - Bindings: make(map[string][]event.Bindable), Trigger: "MouseClickOn", Toggle: nil, @@ -98,7 +96,10 @@ func (g Generator) generate(parent *Generator) Btn { "on": g.R1, "off": g.R2, }) - g.Bindings["MouseClickOn"] = append(g.Bindings["MouseClickOn"], toggleFxn(g)) + g.Bindings = append(g.Bindings, func(caller Btn) event.Binding { + // TODO not default + return event.Bind(event.DefaultBus, mouse.ClickOn, caller, toggleFxn(g)) + }) case g.ListChoice != nil: start := "list" + strconv.Itoa(*g.ListChoice) @@ -109,9 +110,13 @@ func (g Generator) generate(parent *Generator) Btn { } mp["list"+strconv.Itoa(i)] = r } + box = render.NewSwitch(start, mp) - g.Bindings["MouseClickOn"] = append(g.Bindings["MouseClickOn"], listFxn(g)) + g.Bindings = append(g.Bindings, func(caller Btn) event.Binding { + // TODO not default + return event.Bind(event.DefaultBus, mouse.ClickOn, caller, listFxn(g)) + }) case g.R != nil: box = g.R case g.ProgressFunc != nil: @@ -159,56 +164,56 @@ func (g Generator) generate(parent *Generator) Btn { btn = bx } + // TODO: this is impossible with how we've done generics + // Update underlying mousecollision binding to only respect clicks in the shape. // If a finer control is needed then it may make sense to use this as a starting off point // instead of expanding this section. - if g.Shape != nil { + // if g.Shape != nil { + // // extract keys prior to loop as the map will be permuted by the following operations + // keys := make([]string, 0, len(g.Bindings)) + // for k := range g.Bindings { + // // We only really care about mouse events. + // // In some ways this is dangerous of an implementer has defined events that start with mouse... + // // but in that case they might not use g.Shape anyways. + // if !strings.HasPrefix(k, "Mouse") { + // continue + // } + // keys = append(keys, k) + // } + // for _, k := range keys { + // curBind := g.Bindings[k] + // if curBind == nil { + // continue + // } + // // This could cause issues with name collisions but its unlikely and documentation should help make it even more unlikely. + // filteredK := "Filtered" + k + // g.Bindings[filteredK] = g.Bindings[k] + // g.Bindings[k] = []event.Bindable{ + // func(id event.CallerID, button interface{}) int { + // btn := id.E().(Btn) + // mEvent, ok := button.(*mouse.Event) + // // If the passed event is not a mouse event dont filter on location. + // // Main current use case is for nil events passed via simulated clicks. + // if !ok { + // btn.Trigger(filteredK, button) + // } + // bSpace := btn.GetSpace().Bounds() + // if g.Shape.In(int(mEvent.X()-bSpace.Min.X()), int(mEvent.Y()-bSpace.Min.Y()), int(bSpace.W()), int(bSpace.H())) { + // btn.Trigger(filteredK, mEvent) + // } + // return 0 + // }, + // } + // } + // } - // extract keys prior to loop as the map will be permuted by the following operations - keys := make([]string, 0, len(g.Bindings)) - for k := range g.Bindings { - // We only really care about mouse events. - // In some ways this is dangerous of an implementer has defined events that start with mouse... - // but in that case they might not use g.Shape anyways. - if !strings.HasPrefix(k, "Mouse") { - continue - } - keys = append(keys, k) - } - for _, k := range keys { - curBind := g.Bindings[k] - if curBind == nil { - continue - } - // This could cause issues with name collisions but its unlikely and documentation should help make it even more unlikely. - filteredK := "Filtered" + k - g.Bindings[filteredK] = g.Bindings[k] - g.Bindings[k] = []event.Bindable{ - func(id event.CallerID, button interface{}) int { - btn := id.E().(Btn) - mEvent, ok := button.(*mouse.Event) - // If the passed event is not a mouse event dont filter on location. - // Main current use case is for nil events passed via simulated clicks. - if !ok { - btn.Trigger(filteredK, button) - } - bSpace := btn.GetSpace().Bounds() - if g.Shape.In(int(mEvent.X()-bSpace.Min.X()), int(mEvent.Y()-bSpace.Min.Y()), int(bSpace.W()), int(bSpace.H())) { - btn.Trigger(filteredK, mEvent) - } - return 0 - }, - } - } + for _, binding := range g.Bindings { + binding(btn) } - for k, v := range g.Bindings { - for _, b := range v { - btn.Bind(k, b) - } - } - - err := mouse.PhaseCollision(btn.GetSpace()) + // TODO: not default + err := mouse.PhaseCollision(btn.GetSpace(), event.DefaultBus) dlog.ErrorCheck(err) if g.Group != nil { @@ -239,9 +244,8 @@ type switcher interface { } // toggleFxn sets up the mouseclick binding for toggle buttons created for goreport cyclo decrease -func toggleFxn(g Generator) func(id event.CallerID, nothing interface{}) int { - return func(id event.CallerID, nothing interface{}) int { - btn := event.GetEntity(id).(Btn) +func toggleFxn(g Generator) func(btn Btn, payload *mouse.Event) event.Response { + return func(btn Btn, payload *mouse.Event) event.Response { if btn.GetRenderable().(switcher).Get() == "on" { if g.Group != nil && g.Group.active == btn { g.Group.active = nil @@ -253,7 +257,7 @@ func toggleFxn(g Generator) func(id event.CallerID, nothing interface{}) int { g.Group.active = btn for _, b := range g.Group.members { if b.GetRenderable().(switcher).Get() == "on" { - b.Trigger("MouseClickOn", nil) + event.TriggerForCallerOn(event.DefaultBus, b.CID(), mouse.ClickOn, payload) } } } @@ -267,19 +271,16 @@ func toggleFxn(g Generator) func(id event.CallerID, nothing interface{}) int { } // listFxn sets up the mouseclick binding for list buttons created for goreport cyclo reduction -func listFxn(g Generator) func(id event.CallerID, button interface{}) int { - return func(id event.CallerID, button interface{}) int { - btn := event.GetEntity(id).(Btn) +func listFxn(g Generator) func(btn Btn, payload *mouse.Event) event.Response { + return func(btn Btn, payload *mouse.Event) event.Response { i := *g.ListChoice - mEvent := button.(*mouse.Event) - - if mEvent.Button == mouse.ButtonLeft { + if payload.Button == mouse.ButtonLeft { i++ if i == len(g.RS) { i = 0 } - } else if mEvent.Button == mouse.ButtonRight { + } else if payload.Button == mouse.ButtonRight { i-- if i < 0 { i += len(g.RS) diff --git a/entities/x/btn/option.go b/entities/x/btn/option.go index fbb4fbcd..28e7ca87 100644 --- a/entities/x/btn/option.go +++ b/entities/x/btn/option.go @@ -151,15 +151,18 @@ func ToggleList(chosen *int, rs ...render.Modifiable) Option { // Binding appends a function to be called when a specific event // is triggered. -func Binding(s string, bnd event.Bindable) Option { +func Binding[Payload any](ev event.EventID[Payload], bnd event.Bindable[Btn, Payload]) Option { return func(g Generator) Generator { - g.Bindings[s] = append(g.Bindings[s], bnd) + g.Bindings = append(g.Bindings, func(caller Btn) event.Binding { + // TODO: not default + return event.Bind(event.DefaultBus, ev, caller, bnd) + }) return g } } // Click appends a function to be called when the button is clicked on. -func Click(bnd event.Bindable) Option { +func Click(bnd event.Bindable[Btn, *mouse.Event]) Option { return Binding(mouse.ClickOn, bnd) } diff --git a/entities/x/btn/textBox.go b/entities/x/btn/textBox.go index 071637db..68ac4a5e 100644 --- a/entities/x/btn/textBox.go +++ b/entities/x/btn/textBox.go @@ -13,12 +13,6 @@ type TextBox struct { *render.Text } -// Init creates the CID -func (b *TextBox) Init() event.CallerID { - b.CID = event.NextID(b) - return b.CID -} - // NewTextBox creates a textbox func NewTextBox(cid event.CallerID, x, y, w, h, txtX, txtY float64, f *render.Font, r render.Renderable, layers ...int) *TextBox { @@ -28,9 +22,9 @@ func NewTextBox(cid event.CallerID, x, y, w, h, txtX, txtY float64, } b := new(TextBox) - - cid = cid.Parse(b) - + if cid == 0 { + cid = event.DefaultCallerMap.Register(b) + } b.Box = *NewBox(cid, x, y, w, h, r, layers...) b.Text = f.NewText("Init", 0, 0) b.Text.Attach(b.Box.Vector, txtX, txtY) diff --git a/entities/x/force/directionSpace.go b/entities/x/force/directionSpace.go index b7d4cc8e..345b1d70 100644 --- a/entities/x/force/directionSpace.go +++ b/entities/x/force/directionSpace.go @@ -10,11 +10,11 @@ import ( type DirectionSpace struct { *collision.Space physics.ForceVector + event.CallerID } -// Init initializes the DirectionSpace as an entity -func (ds *DirectionSpace) Init() event.CallerID { - return event.NextID(ds) +func (ds DirectionSpace) CID() event.CallerID { + return ds.CallerID } // NewDirectionSpace creates a DirectionSpace and initializes it as an entity. @@ -23,6 +23,7 @@ func NewDirectionSpace(s *collision.Space, v physics.ForceVector) *DirectionSpac Space: s, ForceVector: v, } - s.CID = ds.Init() + // TODO: not default + s.CID = event.DefaultCallerMap.Register(ds) return ds } diff --git a/entities/x/stat/default.go b/entities/x/stat/default.go index cd519ffd..aa6b3aec 100644 --- a/entities/x/stat/default.go +++ b/entities/x/stat/default.go @@ -1,5 +1,7 @@ package stat +import "github.com/oakmound/oak/v3/event" + var ( // DefStatistics is a base set of statistics used by package-level calls // When using multiple statistics, avoid using overlapping event names @@ -7,39 +9,39 @@ var ( ) // Inc triggers an event, incrementing the given statistic by one -func Inc(eventName string) { - DefStatistics.Inc(eventName) +func Inc(ev statEvent) { + DefStatistics.Inc(ev) } // Trigger triggers the given event with a given increment to update a statistic -func Trigger(eventName string, inc int) { - DefStatistics.Trigger(eventName, inc) +func Trigger(ev statEvent, inc int) { + DefStatistics.Trigger(ev, inc) } // TriggerOn triggers the given event, toggling it on -func TriggerOn(eventName string) { - DefStatistics.TriggerOn(eventName) +func TriggerOn(ev timedStatEvent) { + DefStatistics.TriggerOn(ev) } // TriggerOff triggers the given event, toggling it off -func TriggerOff(eventName string) { - DefStatistics.TriggerOff(eventName) +func TriggerOff(ev timedStatEvent) { + DefStatistics.TriggerOff(ev) } // TriggerTimed triggers the given event, toggling it on or off -func TriggerTimed(eventName string, on bool) { - DefStatistics.TriggerTimed(eventName, on) +func TriggerTimed(ev timedStatEvent, on bool) { + DefStatistics.TriggerTimed(ev, on) } // TrackStats records a stat event to the Statistics map and creates the statistic if it does not already exist -func TrackStats(no int, data interface{}) int { +func TrackStats(no int, data interface{}) event.Response { return DefStatistics.TrackStats(no, data) } // TrackTimeStats acts like TrackStats, but tracks durations of events. If the // event has not started, it logs a start time, and then when the event ends // it will log the delta since the start. -func TrackTimeStats(no int, data interface{}) int { +func TrackTimeStats(no int, data interface{}) event.Response { return DefStatistics.TrackTimeStats(no, data) } diff --git a/entities/x/stat/statistic.go b/entities/x/stat/statistic.go index 970fa494..eb222f2b 100644 --- a/entities/x/stat/statistic.go +++ b/entities/x/stat/statistic.go @@ -70,11 +70,11 @@ func (st *Statistics) trackStats(name string, val int) { } // TrackStats records a stat event to the Statistics map and creates the statistic if it does not already exist -func (st *Statistics) TrackStats(no int, data interface{}) int { +func (st *Statistics) TrackStats(no int, data interface{}) event.Response { stat, ok := data.(stat) if !ok { dlog.Error("TrackStats called with a non-stat payload") - return event.UnbindEvent + return event.UnbindThis } st.trackStats(stat.name, stat.inc) return 0 @@ -83,11 +83,11 @@ func (st *Statistics) TrackStats(no int, data interface{}) int { // TrackTimeStats acts like TrackStats, but tracks durations of events. If the // event has not started, it logs a start time, and then when the event ends // it will log the delta since the start. -func (st *Statistics) TrackTimeStats(no int, data interface{}) int { +func (st *Statistics) TrackTimeStats(no int, data interface{}) event.Response { timed, ok := data.(timedStat) if !ok { dlog.Error("TrackTimeStats called with a non-timedStat payload") - return event.UnbindEvent + return event.UnbindThis } if timed.on { //Turning on a thing to time track st.statTimeLock.Lock() diff --git a/entities/x/stat/stats.go b/entities/x/stat/stats.go index d0c09a03..6710c681 100644 --- a/entities/x/stat/stats.go +++ b/entities/x/stat/stats.go @@ -1,63 +1,81 @@ package stat -import "github.com/oakmound/oak/v3/event" +import ( + "fmt" + + "github.com/oakmound/oak/v3/event" +) + +// TODO: these functions are useless unless the types are exported, and +// if the types are exported the api is bad + +type timedStatEvent struct { + event event.EventID[timedStat] + fmt.Stringer +} type timedStat struct { name string on bool } + +type statEvent struct { + event event.EventID[stat] + fmt.Stringer +} + type stat struct { name string inc int } // TimedOn returns a binding that will trigger toggling on the given event -func TimedOn(eventName string) event.Bindable { - return TimedBind(eventName, true) +func TimedOn(ev timedStatEvent) event.UnsafeBindable { + return TimedBind(ev, true) } // TimedOff returns a binding that will trigger toggling off the given event -func TimedOff(eventName string) event.Bindable { - return TimedBind(eventName, false) +func TimedOff(ev timedStatEvent) event.UnsafeBindable { + return TimedBind(ev, false) } // TimedBind returns a binding that will trigger toggling on or off the given event -func TimedBind(eventName string, on bool) event.Bindable { - return func(event.CallerID, interface{}) int { - event.Trigger(eventName, timedStat{eventName, on}) +func TimedBind(ev timedStatEvent, on bool) event.UnsafeBindable { + return func(event.CallerID, event.Handler, interface{}) event.Response { + event.TriggerOn(event.DefaultBus, ev.event, timedStat{ev.String(), on}) return 0 } } // Bind returns a binding that will increment the given event by 'inc' -func Bind(eventName string, inc int) event.Bindable { - return func(event.CallerID, interface{}) int { - event.Trigger(eventName, stat{eventName, inc}) +func Bind(ev statEvent, inc int) event.UnsafeBindable { + return func(event.CallerID, event.Handler, interface{}) event.Response { + event.TriggerOn(event.DefaultBus, ev.event, stat{ev.String(), inc}) return 0 } } // Inc triggers an event, incrementing the given statistic by one -func (st *Statistics) Inc(eventName string) { - st.Trigger(eventName, 1) +func (st *Statistics) Inc(ev statEvent) { + st.Trigger(ev, 1) } // Trigger triggers the given event with a given increment to update a statistic -func (st *Statistics) Trigger(eventName string, inc int) { - event.Trigger(eventName, stat{eventName, inc}) +func (st *Statistics) Trigger(ev statEvent, inc int) { + event.TriggerOn(event.DefaultBus, ev.event, stat{ev.String(), inc}) } // TriggerOn triggers the given event, toggling it on -func (st *Statistics) TriggerOn(eventName string) { - st.TriggerTimed(eventName, true) +func (st *Statistics) TriggerOn(ev timedStatEvent) { + st.TriggerTimed(ev, true) } // TriggerOff triggers the given event, toggling it off -func (st *Statistics) TriggerOff(eventName string) { - st.TriggerTimed(eventName, false) +func (st *Statistics) TriggerOff(ev timedStatEvent) { + st.TriggerTimed(ev, false) } // TriggerTimed triggers the given event, toggling it on or off -func (st *Statistics) TriggerTimed(eventName string, on bool) { - event.Trigger(eventName, timedStat{eventName, on}) +func (st *Statistics) TriggerTimed(ev timedStatEvent, on bool) { + event.TriggerOn(event.DefaultBus, ev.event, timedStat{ev.String(), on}) } From d8f60a026365e215f2d86f2e04ae3f15116b9581 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Mon, 28 Mar 2022 20:34:19 -0500 Subject: [PATCH 20/41] event: adjust docs --- event/bind.go | 10 +++++++++- event/bus.go | 6 ++++-- event/caller.go | 3 +++ event/default.go | 7 +++++-- event/events.go | 7 +++++-- event/handler.go | 1 - event/trigger.go | 7 +++++-- 7 files changed, 31 insertions(+), 10 deletions(-) diff --git a/event/bind.go b/event/bind.go index 6e1da0c3..ce26e2a6 100644 --- a/event/bind.go +++ b/event/bind.go @@ -6,7 +6,8 @@ import "sync/atomic" // A: For concurrent safety, most operations on a bus lock the bus. Triggers acquire a read lock on the bus, // as they iterate over internal bus components. Most logic within an event bus will happen from within // a Trigger call-- when an entity is destroyed by some collision, for example, all of its bindings should -// be unregistered. If one were to call Unbind from within a +// be unregistered. If one were to call Unbind from within a call to Trigger, the trigger would never release +// its lock-- so the unbind would never be able to take the lock-- so the bus would be unrecoverably stuck. // Q: Why not trust users to call Bind / Unbind / etc with `go`, to allow the caller to decide when to use // concurrency? @@ -89,6 +90,9 @@ func (bus *Bus) Unbind(loc Binding) { // with an event registered via RegisterEvent. type Bindable[C any, Payload any] func(C, Payload) Response +// Bind will cause the function fn to be called whenever the event ev is triggered on the given event handler. The function +// will be called with the provided caller as its first argument, and will also be called when the provided event is specifically +// triggered on the caller's ID. func Bind[C Caller, Payload any](h Handler, ev EventID[Payload], caller C, fn Bindable[C, Payload]) Binding { return h.UnsafeBind(ev.UnsafeEventID, caller.CID(), func(cid CallerID, h Handler, payload interface{}) Response { typedPayload := payload.(Payload) @@ -98,8 +102,10 @@ func Bind[C Caller, Payload any](h Handler, ev EventID[Payload], caller C, fn Bi }) } +// A GlobalBindable is a bindable that is not bound to a specific caller. type GlobalBindable[Payload any] func(Payload) Response +// GlobalBind will cause the function fn to be called whenever the event ev is triggered on the given event handler. func GlobalBind[Payload any](h Handler, ev EventID[Payload], fn GlobalBindable[Payload]) Binding { return h.UnsafeBind(ev.UnsafeEventID, Global, func(cid CallerID, h Handler, payload interface{}) Response { typedPayload := payload.(Payload) @@ -110,6 +116,7 @@ func GlobalBind[Payload any](h Handler, ev EventID[Payload], fn GlobalBindable[P // UnsafeBindable defines the underlying signature of all bindings. type UnsafeBindable func(CallerID, Handler, interface{}) Response +// EmptyBinding is shorthand for an UnsafeBindable that does not accept or return anything. func EmptyBinding(f func()) UnsafeBindable { return func(CallerID, Handler, interface{}) Response { f() @@ -117,6 +124,7 @@ func EmptyBinding(f func()) UnsafeBindable { } } +// UnbindAllFrom unbinds all bindings currently bound to the provided caller via ID. func (bus *Bus) UnbindAllFrom(c CallerID) { go func() { bus.mutex.Lock() diff --git a/event/bus.go b/event/bus.go index e99ef700..91ad7101 100644 --- a/event/bus.go +++ b/event/bus.go @@ -7,7 +7,7 @@ import ( "github.com/oakmound/oak/v3/oakerr" ) -// A Bus stores bindables to be triggered by events +// A Bus stores bindables to be triggered by events. type Bus struct { nextBindID *int64 bindingMap map[UnsafeEventID]map[CallerID]bindableList @@ -98,7 +98,8 @@ func (bus *Bus) EnterLoop(frameDelay time.Duration) { }() } -// Stop ceases anything spawned by an ongoing UpdateLoop +// Stop ceases anything spawned by an ongoing EnterLoop. It will panic if called without EnterLoop being called first, +// or if called twice without an EnterLoop between the two calls. func (bus *Bus) Stop() error { if bus.ticker != nil { bus.ticker.Stop() @@ -120,6 +121,7 @@ func (bus *Bus) SetEnterLoopRate(frameDelay time.Duration) error { return nil } +// GetCallerMap returns this bus's caller map. func (b *Bus) GetCallerMap() *CallerMap { return b.callerMap } diff --git a/event/caller.go b/event/caller.go index b9b9bc31..1be9ac64 100644 --- a/event/caller.go +++ b/event/caller.go @@ -13,6 +13,9 @@ func (c CallerID) CID() CallerID { return c } +// Global is the CallerID associated with global bindings. A caller must not be assigned +// this ID. Global may be used to manually create bindings scoped to no callers, but the GlobalBind function +// should be preferred when possible for type safety. const Global CallerID = 0 type Caller interface { diff --git a/event/default.go b/event/default.go index fdd1015f..9f82cd56 100644 --- a/event/default.go +++ b/event/default.go @@ -1,9 +1,12 @@ package event +// DefaultBus is a global Bus. It uses the DefaultCallerMap internally. It should not be used unless your program is only +// using a single Bus. Preferably multi-bus programs would create their own buses and caller maps specific to each bus's +// use. var DefaultBus *Bus -// DefaultCallerMap is the caller map used by all event package caller -// functions. +// DefaultCallerMap is a global CallerMap. It should not be used unless your program is only using a single CallerMap, +// or in other words definitely only has one event bus running at a time. var DefaultCallerMap *CallerMap func init() { diff --git a/event/events.go b/event/events.go index 83515072..e4c75f65 100644 --- a/event/events.go +++ b/event/events.go @@ -7,8 +7,10 @@ import ( "github.com/oakmound/oak/v3/alg/intgeom" ) +// An UnsafeEventID is a non-typed eventID. EventIDs are just these, with type information attached. type UnsafeEventID int64 +// A EventID represents an event associated with a given payload type. type EventID[T any] struct { UnsafeEventID } @@ -17,8 +19,8 @@ var ( nextEventID int64 ) -const NoEvent = 0 - +// RegisterEvent returns a unique ID to associate an event with. EventIDs not created through RegisterEvent are +// not valid for use in type-safe bindings. func RegisterEvent[T any]() EventID[T] { id := atomic.AddInt64(&nextEventID, 1) return EventID[T]{ @@ -26,6 +28,7 @@ func RegisterEvent[T any]() EventID[T] { } } +// NoPayload is an alias for the empty struct. type NoPayload = struct{} // EnterPayload is the payload sent down to Enter bindings diff --git a/event/handler.go b/event/handler.go index c5a13613..40f2ac88 100644 --- a/event/handler.go +++ b/event/handler.go @@ -13,7 +13,6 @@ var ( // by alternative event handlers. type Handler interface { EnterLoop(time.Duration) - SetEnterLoopRate(time.Duration) error Stop() error Reset() TriggerForCaller(cid CallerID, event UnsafeEventID, data interface{}) chan struct{} diff --git a/event/trigger.go b/event/trigger.go index 66322b21..c65c966a 100644 --- a/event/trigger.go +++ b/event/trigger.go @@ -1,7 +1,6 @@ package event -// Trigger will scan through the event bus and call all bindables found attached -// to the given event, with the passed in data. +// TriggerForCaller acts like Trigger, but will only trigger for the given caller. func (bus *Bus) TriggerForCaller(callerID CallerID, eventID UnsafeEventID, data interface{}) chan struct{} { if callerID == Global { return bus.Trigger(eventID, data) @@ -20,6 +19,8 @@ func (bus *Bus) TriggerForCaller(callerID CallerID, eventID UnsafeEventID, data return ch } +// Trigger will scan through the event bus and call all bindables found attached +// to the given event, with the passed in data. func (bus *Bus) Trigger(eventID UnsafeEventID, data interface{}) chan struct{} { ch := make(chan struct{}) go func() { @@ -33,10 +34,12 @@ func (bus *Bus) Trigger(eventID UnsafeEventID, data interface{}) chan struct{} { return ch } +// TriggerOn calls Trigger with a strongly typed event. func TriggerOn[T any](b Handler, ev EventID[T], data T) chan struct{} { return b.Trigger(ev.UnsafeEventID, data) } +// TriggerForCallerOn calls TriggerForCaller with a strongly typed event. func TriggerForCallerOn[T any](b Handler, cid CallerID, ev EventID[T], data T) chan struct{} { return b.TriggerForCaller(cid, ev.UnsafeEventID, data) } From 382dded3f57608ccfb771fc292c8c9b263068eb7 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Wed, 30 Mar 2022 17:48:59 -0500 Subject: [PATCH 21/41] event: add tests for trigger --- event/bind.go | 18 +-- event/internal.go | 12 -- event/trigger_test.go | 255 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 261 insertions(+), 24 deletions(-) create mode 100644 event/trigger_test.go diff --git a/event/bind.go b/event/bind.go index ce26e2a6..2ab7aef1 100644 --- a/event/bind.go +++ b/event/bind.go @@ -11,7 +11,7 @@ import "sync/atomic" // Q: Why not trust users to call Bind / Unbind / etc with `go`, to allow the caller to decide when to use // concurrency? -// A: It is almost never correct to not call these functions with `go`, and it is a bad user experience for +// A: It is almost never correct to not call such a function with `go`, and it is a bad user experience for // the engine to deadlock unexpectedly because you forgot to begin some call with a goroutine. // A Binding, returned from calls to Bind, references the details of a binding and where that binding is @@ -24,7 +24,7 @@ type Binding struct { CallerID CallerID BindID BindID // Bound is closed once the binding has been applied. Wait on this condition carefully; bindings - // will not take effect while an event is being triggered (e.g. in a even callback's returning thread) + // will not take effect while an event is being triggered (e.g. in a event callback's returning thread) Bound <-chan struct{} } @@ -46,7 +46,8 @@ func (bus *Bus) UnsafeBind(eventID UnsafeEventID, callerID CallerID, fn UnsafeBi ch := make(chan struct{}) go func() { bus.mutex.Lock() - bus.getBindableList(eventID, callerID).storeBindable(fn, bindID) + bl := bus.getBindableList(eventID, callerID) + bl[bindID] = fn bus.mutex.Unlock() close(ch) }() @@ -81,7 +82,8 @@ func (bus *Bus) PersistentBind(eventID UnsafeEventID, callerID CallerID, fn Unsa func (bus *Bus) Unbind(loc Binding) { go func() { bus.mutex.Lock() - bus.getBindableList(loc.EventID, loc.CallerID).remove(loc.BindID) + l := bus.getBindableList(loc.EventID, loc.CallerID) + delete(l, loc.BindID) bus.mutex.Unlock() }() } @@ -116,14 +118,6 @@ func GlobalBind[Payload any](h Handler, ev EventID[Payload], fn GlobalBindable[P // UnsafeBindable defines the underlying signature of all bindings. type UnsafeBindable func(CallerID, Handler, interface{}) Response -// EmptyBinding is shorthand for an UnsafeBindable that does not accept or return anything. -func EmptyBinding(f func()) UnsafeBindable { - return func(CallerID, Handler, interface{}) Response { - f() - return NoResponse - } -} - // UnbindAllFrom unbinds all bindings currently bound to the provided caller via ID. func (bus *Bus) UnbindAllFrom(c CallerID) { go func() { diff --git a/event/internal.go b/event/internal.go index a27fcbb3..55e3995e 100644 --- a/event/internal.go +++ b/event/internal.go @@ -4,14 +4,6 @@ import "sync" type bindableList map[BindID]UnsafeBindable -func (bl bindableList) storeBindable(fn UnsafeBindable, bindID BindID) { - bl[bindID] = fn -} - -func (bl bindableList) remove(bindID BindID) { - delete(bl, bindID) -} - func (eb *Bus) getBindableList(eventID UnsafeEventID, callerID CallerID) bindableList { if m := eb.bindingMap[eventID]; m == nil { eb.bindingMap[eventID] = make(map[CallerID]bindableList) @@ -33,10 +25,6 @@ func (bus *Bus) trigger(binds bindableList, eventID UnsafeEventID, callerID Call for bindID, bnd := range binds { bindID := bindID bnd := bnd - if bnd == nil { - wg.Done() - continue - } go func() { if callerID == Global || bus.callerMap.HasEntity(callerID) { response := bnd(callerID, bus, data) diff --git a/event/trigger_test.go b/event/trigger_test.go new file mode 100644 index 00000000..fd3cf0c6 --- /dev/null +++ b/event/trigger_test.go @@ -0,0 +1,255 @@ +package event_test + +import ( + "fmt" + "math/rand" + "os" + "testing" + "time" + + "github.com/oakmound/oak/v3/event" +) + +func TestMain(m *testing.M) { + rand.Seed(time.Now().UnixNano()) + os.Exit(m.Run()) +} + +func TestBus_TriggerForCaller(t *testing.T) { + t.Run("NoBinding", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + id := event.UnsafeEventID(rand.Intn(100000)) + ch := b.TriggerForCaller(0, id, nil) + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for trigger to close channel") + case <-ch: + } + }) + t.Run("GlobalWithBinding", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + id := event.UnsafeEventID(rand.Intn(100000)) + errs := make(chan error) + binding := b.UnsafeBind(id, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + defer close(errs) + if ci != 0 { + errs <- expectedError("callerID", 0, ci) + } + if h != b { + errs <- expectedError("bus", b, h) + } + if i != nil { + errs <- expectedError("payload", nil, i) + } + return 0 + }) + _ = binding + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for bind to close channel") + case <-binding.Bound: + } + ch := b.TriggerForCaller(0, id, nil) + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for trigger to close channel") + case <-ch: + } + for err := range errs { + t.Error(err) + } + }) + t.Run("WithMissingCallerID", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + id := event.UnsafeEventID(rand.Intn(100000)) + callerID := event.CallerID(rand.Intn(100000)) + errs := make(chan error) + binding := b.UnsafeBind(id, callerID, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + errs <- fmt.Errorf("binding should not be triggered") + return 0 + }) + _ = binding + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for bind to close channel") + case <-binding.Bound: + } + ch := b.TriggerForCaller(callerID, id, nil) + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for trigger to close channel") + case <-ch: + } + select { + case err := <-errs: + t.Error(err) + default: + } + }) + t.Run("WithValidCallerID", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + var cid event.CallerID + callerID := b.GetCallerMap().Register(cid) + id := event.UnsafeEventID(rand.Intn(100000)) + errs := make(chan error) + binding := b.UnsafeBind(id, callerID, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + defer close(errs) + if ci != callerID { + errs <- expectedError("callerID", callerID, ci) + } + if h != b { + errs <- expectedError("bus", b, h) + } + if i != nil { + errs <- expectedError("payload", nil, i) + } + return 0 + }) + _ = binding + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for bind to close channel") + case <-binding.Bound: + } + ch := b.TriggerForCaller(callerID, id, nil) + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for trigger to close channel") + case <-ch: + } + for err := range errs { + t.Error(err) + } + }) +} + +func TestBus_Trigger(t *testing.T) { + t.Run("NoBinding", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + id := event.UnsafeEventID(rand.Intn(100000)) + ch := b.Trigger(id, nil) + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for trigger to close channel") + case <-ch: + } + }) + t.Run("GlobalWithBinding", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + id := event.UnsafeEventID(rand.Intn(100000)) + errs := make(chan error) + binding := b.UnsafeBind(id, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + defer close(errs) + if ci != 0 { + errs <- expectedError("callerID", 0, ci) + } + if h != b { + errs <- expectedError("bus", b, h) + } + if i != nil { + errs <- expectedError("payload", nil, i) + } + return 0 + }) + _ = binding + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for bind to close channel") + case <-binding.Bound: + } + ch := b.Trigger(id, nil) + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for trigger to close channel") + case <-ch: + } + for err := range errs { + t.Error(err) + } + }) + t.Run("WithMissingCallerID", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + id := event.UnsafeEventID(rand.Intn(100000)) + callerID := rand.Intn(100000) + errs := make(chan error) + binding := b.UnsafeBind(id, event.CallerID(callerID), func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + errs <- fmt.Errorf("binding should not be triggered") + return 0 + }) + _ = binding + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for bind to close channel") + case <-binding.Bound: + } + ch := b.Trigger(id, nil) + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for trigger to close channel") + case <-ch: + } + select { + case err := <-errs: + t.Error(err) + default: + } + }) + t.Run("WithValidCallerID", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + var cid event.CallerID + callerID := b.GetCallerMap().Register(cid) + id := event.UnsafeEventID(rand.Intn(100000)) + errs := make(chan error) + binding := b.UnsafeBind(id, event.CallerID(callerID), func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + defer close(errs) + if ci != callerID { + errs <- expectedError("callerID", callerID, ci) + } + if h != b { + errs <- expectedError("bus", b, h) + } + if i != nil { + errs <- expectedError("payload", nil, i) + } + return 0 + }) + _ = binding + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for bind to close channel") + case <-binding.Bound: + } + ch := b.Trigger(id, nil) + select { + case <-time.After(50 * time.Millisecond): + t.Fatal("timeout waiting for trigger to close channel") + case <-ch: + } + for err := range errs { + t.Error(err) + } + }) +} + +// TriggerOn and TriggerForCallerOn are simple wrappers of the tested methods above, so +// they are not tested thoroughly. + +func TestTriggerOn(t *testing.T) { + t.Run("SuperficialCoverage", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + eventID := event.RegisterEvent[struct{}]() + event.TriggerOn(b, eventID, struct{}{}) + }) +} + +func TestTriggerForCallerOn(t *testing.T) { + t.Run("SuperficialCoverage", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + eventID := event.RegisterEvent[struct{}]() + event.TriggerForCallerOn(b, 0, eventID, struct{}{}) + }) +} + +func expectedError(name string, expected, got interface{}) error { + return fmt.Errorf("expected %s to be %v, got %v", name, expected, got) +} From 91ef92458e6befe650cd01902a9017677bfcb5da Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Wed, 30 Mar 2022 23:09:38 -0500 Subject: [PATCH 22/41] event: add most tests, further trim down API, ensure concurrent-safe use --- debugstream/scopeHelper.go | 4 +- entities/doodad.go | 4 +- entities/x/stat/statistic.go | 4 +- event/bind.go | 29 +++++++--- event/bind_test.go | 70 ++++++++++++++++++++++++ event/bus.go | 97 +++++++++++++++------------------ event/bus_test.go | 99 ++++++++++++++++++++++++++++++++++ event/caller.go | 36 ++++++++----- event/caller_test.go | 67 +++++++++++++++++++++++ event/events.go | 13 ----- event/handler.go | 6 --- event/internal.go | 8 ++- event/response.go | 10 ++-- event/response_test.go | 9 ++++ event/trigger_test.go | 1 - inputLoop.go | 20 +++++-- inputTracker.go | 6 +-- mouse/onCollision.go | 2 +- render/particle/source.go | 4 +- render/particle/source_test.go | 2 +- render/sequence.go | 4 +- render/sequence_test.go | 2 +- sceneLoop.go | 5 +- viewport.go | 2 +- 24 files changed, 379 insertions(+), 125 deletions(-) create mode 100644 event/bind_test.go create mode 100644 event/bus_test.go create mode 100644 event/caller_test.go create mode 100644 event/response_test.go diff --git a/debugstream/scopeHelper.go b/debugstream/scopeHelper.go index 7104b0b6..465e3c93 100644 --- a/debugstream/scopeHelper.go +++ b/debugstream/scopeHelper.go @@ -90,14 +90,14 @@ func mouseDetails(w window.Window) func(*mouse.Event) event.Response { if len(results) > 0 { i := results[0].CID if i > 0 && cm.HasEntity(i) { - e := cm.GetEntity(i) + e := cm.HasEntity(i) fmt.Printf("%+v\n", e) } else { fmt.Println("No entity ", i) } } - return event.UnbindThis + return event.ResponseUnbindThisBinding } } diff --git a/entities/doodad.go b/entities/doodad.go index bef8722a..43c5f86d 100644 --- a/entities/doodad.go +++ b/entities/doodad.go @@ -43,14 +43,14 @@ func (d *Doodad) Destroy() { d.R.Undraw() } event.DefaultBus.UnbindAllFrom(d.CallerID) - event.DefaultCallerMap.DestroyEntity(d.CallerID) + event.DefaultCallerMap.RemoveEntity(d.CallerID) } // Overwrites // SetPos both Sets logical position and renderable position // The need for this sort of function is lessened with the introduction -// of vector attachement. +// of vector attachment. func (d *Doodad) SetPos(x, y float64) { d.SetLogicPos(x, y) if d.R != nil { diff --git a/entities/x/stat/statistic.go b/entities/x/stat/statistic.go index eb222f2b..9d1b0b7d 100644 --- a/entities/x/stat/statistic.go +++ b/entities/x/stat/statistic.go @@ -74,7 +74,7 @@ func (st *Statistics) TrackStats(no int, data interface{}) event.Response { stat, ok := data.(stat) if !ok { dlog.Error("TrackStats called with a non-stat payload") - return event.UnbindThis + return event.ResponseUnbindThisBinding } st.trackStats(stat.name, stat.inc) return 0 @@ -87,7 +87,7 @@ func (st *Statistics) TrackTimeStats(no int, data interface{}) event.Response { timed, ok := data.(timedStat) if !ok { dlog.Error("TrackTimeStats called with a non-timedStat payload") - return event.UnbindThis + return event.ResponseUnbindThisBinding } if timed.on { //Turning on a thing to time track st.statTimeLock.Lock() diff --git a/event/bind.go b/event/bind.go index 2ab7aef1..38c31010 100644 --- a/event/bind.go +++ b/event/bind.go @@ -23,6 +23,9 @@ type Binding struct { EventID UnsafeEventID CallerID CallerID BindID BindID + + busResetCount int64 + // Bound is closed once the binding has been applied. Wait on this condition carefully; bindings // will not take effect while an event is being triggered (e.g. in a event callback's returning thread) Bound <-chan struct{} @@ -42,21 +45,27 @@ type BindID int64 // available to be triggered. When Reset is called on a Bus, all prior bindings are unbound. This // call is 'unsafe' because UnsafeBindables use bare interface{} types. func (bus *Bus) UnsafeBind(eventID UnsafeEventID, callerID CallerID, fn UnsafeBindable) Binding { + expectedResetCount := bus.resetCount bindID := BindID(atomic.AddInt64(bus.nextBindID, 1)) ch := make(chan struct{}) go func() { + defer close(ch) bus.mutex.Lock() + defer bus.mutex.Unlock() + if bus.resetCount != expectedResetCount { + // The event bus has reset while we we were waiting to bind this + return + } bl := bus.getBindableList(eventID, callerID) bl[bindID] = fn - bus.mutex.Unlock() - close(ch) }() return Binding{ - Handler: bus, - EventID: eventID, - CallerID: callerID, - BindID: bindID, - Bound: ch, + Handler: bus, + EventID: eventID, + CallerID: callerID, + BindID: bindID, + Bound: ch, + busResetCount: bus.resetCount, } } @@ -82,9 +91,13 @@ func (bus *Bus) PersistentBind(eventID UnsafeEventID, callerID CallerID, fn Unsa func (bus *Bus) Unbind(loc Binding) { go func() { bus.mutex.Lock() + defer bus.mutex.Unlock() + if bus.resetCount != loc.busResetCount { + // This binding is not valid for this bus (in this state) + return + } l := bus.getBindableList(loc.EventID, loc.CallerID) delete(l, loc.BindID) - bus.mutex.Unlock() }() } diff --git a/event/bind_test.go b/event/bind_test.go new file mode 100644 index 00000000..fc96108e --- /dev/null +++ b/event/bind_test.go @@ -0,0 +1,70 @@ +package event_test + +import ( + "sync/atomic" + "testing" + + "github.com/oakmound/oak/v3/event" +) + +func TestBus_UnsafeBind(t *testing.T) { + t.Run("ConcurrentReset", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + + var calls int32 + for i := 0; i < 1000; i++ { + b.UnsafeBind(1, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + atomic.AddInt32(&calls, 1) + return 0 + }) + b.Reset() + // No matter what happens with thread scheduling above, this trigger should never increment calls + <-b.Trigger(1, nil) + } + if calls != 0 { + t.Fatal("a pre-reset binding was triggered after a bus reset") + } + }) +} + +func TestBus_Unbind(t *testing.T) { + t.Run("ConcurrentReset", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + + var goodCalls int32 + for i := 0; i < 1000; i++ { + b1 := b.UnsafeBind(1, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + return 0 + }) + b.Unbind(b1) + b.Reset() + // b1 and b2 will share a bindID + b2 := b.UnsafeBind(1, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + atomic.AddInt32(&goodCalls, 1) + return 0 + }) + <-b2.Bound + <-b.Trigger(1, nil) + b2.Unbind() + } + if goodCalls != 1000 { + t.Fatal("a pre-reset unbind unbound a post-reset binding", goodCalls) + } + }) +} + +func TestBind(t *testing.T) { + t.Run("SuperficialCoverage", func(t *testing.T) { + t.Skip("TODO") + }) +} + +func TestGlobalBind(t *testing.T) { + t.Run("SuperficialCoverage", func(t *testing.T) { + t.Skip("TODO") + }) +} + +func TestBus_UnbindAllFrom(t *testing.T) { + t.Skip("TODO") +} diff --git a/event/bus.go b/event/bus.go index 91ad7101..e47f7502 100644 --- a/event/bus.go +++ b/event/bus.go @@ -3,19 +3,22 @@ package event import ( "sync" "time" - - "github.com/oakmound/oak/v3/oakerr" ) // A Bus stores bindables to be triggered by events. type Bus struct { - nextBindID *int64 + // nextBindID is an atomically incrementing value to track bindings within this structure + nextBindID *int64 + + // resetCount increments every time the bus is reset. bindings and unbindings make sure that + // they are called on a bus with an unchanged reset count, and become NOPs if performed on + // a bus with a different reset count to ensure they do not interfere with a bus using different + // bind IDs. + resetCount int64 bindingMap map[UnsafeEventID]map[CallerID]bindableList persistentBindings []persistentBinding - doneCh chan struct{} - framesElapsed int - ticker *time.Ticker - callerMap *CallerMap + + callerMap *CallerMap mutex sync.RWMutex } @@ -36,7 +39,6 @@ func NewBus(callerMap *CallerMap) *Bus { return &Bus{ nextBindID: new(int64), bindingMap: make(map[UnsafeEventID]map[CallerID]bindableList), - doneCh: make(chan struct{}), callerMap: callerMap, } } @@ -46,6 +48,11 @@ func (bus *Bus) SetCallerMap(cm *CallerMap) { bus.callerMap = cm } +// GetCallerMap returns this bus's caller map. +func (b *Bus) GetCallerMap() *CallerMap { + return b.callerMap +} + // ClearPersistentBindings removes all persistent bindings. It will not unbind them // from the bus, but they will not be bound following the next bus reset. func (eb *Bus) ClearPersistentBindings() { @@ -54,74 +61,54 @@ func (eb *Bus) ClearPersistentBindings() { eb.mutex.Unlock() } -// Reset unbinds all present, non-persistent bindings on the bus. +// Reset unbinds all present, non-persistent bindings on the bus. It will block until +// persistent bindings are in place. func (bus *Bus) Reset() { bus.mutex.Lock() + bus.resetCount++ bus.bindingMap = make(map[UnsafeEventID]map[CallerID]bindableList) - for _, pb := range bus.persistentBindings { - bus.UnsafeBind(pb.eventID, pb.callerID, pb.fn) + repersist := make([]Binding, len(bus.persistentBindings)) + for i, pb := range bus.persistentBindings { + repersist[i] = bus.UnsafeBind(pb.eventID, pb.callerID, pb.fn) } bus.mutex.Unlock() + for _, bnd := range repersist { + <-bnd.Bound + } } -// EnterLoop triggers Enter events at the specified rate -func (bus *Bus) EnterLoop(frameDelay time.Duration) { - // The logical loop. - // In order, it waits on receiving a signal to begin a logical frame. - // It then runs any functions bound to when a frame begins. - // It then allows a scene to perform it's loop operation. - bus.framesElapsed = 0 - if bus.ticker == nil { - bus.ticker = time.NewTicker(frameDelay) - } else { - bus.ticker.Reset(frameDelay) - } - bus.doneCh = make(chan struct{}) +// EnterLoop triggers Enter events at the specified rate until the returned cancel is called. +func EnterLoop(bus Handler, frameDelay time.Duration) (cancel func()) { + ch := make(chan struct{}) go func() { + ticker := time.NewTicker(frameDelay) frameDelayF64 := float64(frameDelay) lastTick := time.Now() + framesElapsed := 0 for { select { - case now := <-bus.ticker.C: + case now := <-ticker.C: deltaTime := now.Sub(lastTick) lastTick = now <-bus.Trigger(Enter.UnsafeEventID, EnterPayload{ - FramesElapsed: bus.framesElapsed, + FramesElapsed: framesElapsed, SinceLastFrame: deltaTime, TickPercent: float64(deltaTime) / frameDelayF64, }) - bus.framesElapsed++ - case <-bus.doneCh: + framesElapsed++ + case <-ch: + ticker.Stop() return } } }() -} - -// Stop ceases anything spawned by an ongoing EnterLoop. It will panic if called without EnterLoop being called first, -// or if called twice without an EnterLoop between the two calls. -func (bus *Bus) Stop() error { - if bus.ticker != nil { - bus.ticker.Stop() - } - close(bus.doneCh) - return nil -} - -// SetTick optionally updates the Logical System’s tick rate -// (while it is looping) to be frameRate. If this operation is not -// supported, it should return an error. -func (bus *Bus) SetEnterLoopRate(frameDelay time.Duration) error { - if bus.ticker == nil { - return oakerr.NotFound{ - InputName: "bus.ticker", - } + return func() { + // Q: why send here as well as close + // A: to ensure that no more ticks are sent, the above goroutine has to + // acknowledge that it should stop and return-- just closing would + // enable code following this cancel function to assume no enters were + // being triggered when they still are. + ch <- struct{}{} + close(ch) } - bus.ticker.Reset(frameDelay) - return nil -} - -// GetCallerMap returns this bus's caller map. -func (b *Bus) GetCallerMap() *CallerMap { - return b.callerMap } diff --git a/event/bus_test.go b/event/bus_test.go new file mode 100644 index 00000000..8b227ddb --- /dev/null +++ b/event/bus_test.go @@ -0,0 +1,99 @@ +package event_test + +import ( + "math/rand" + "sync/atomic" + "testing" + "time" + + "github.com/oakmound/oak/v3/event" +) + +func TestNewBus(t *testing.T) { + t.Run("DefaultCallerMap", func(t *testing.T) { + b := event.NewBus(nil) + if b.GetCallerMap() != event.DefaultCallerMap { + t.Fatal("nil caller map not turned into default caller map") + } + }) + t.Run("Basic", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + if b == nil { + t.Fatal("NewBus created nil bus") + } + }) +} + +func TestBus_SetCallerMap(t *testing.T) { + t.Run("Basic", func(t *testing.T) { + cm1 := event.NewCallerMap() + b := event.NewBus(cm1) + c1 := event.CallerID(rand.Intn(10000)) + b.GetCallerMap().Register(c1) + cm2 := event.NewCallerMap() + b.SetCallerMap(cm2) + if b.GetCallerMap().HasEntity(c1) { + t.Fatal("event had old entity after changed caller map") + } + }) +} + +func TestBus_ClearPersistentBindings(t *testing.T) { + t.Run("Basic", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + var impersistentCalls int32 + var persistentCalls int32 + b1 := b.UnsafeBind(1, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + atomic.AddInt32(&impersistentCalls, 1) + return 0 + }) + b2 := b.PersistentBind(1, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + atomic.AddInt32(&persistentCalls, 1) + return 0 + }) + <-b1.Bound + <-b2.Bound + <-b.Trigger(1, nil) + if impersistentCalls != 1 { + t.Fatal(expectedError("impersistent calls", 1, impersistentCalls)) + } + if persistentCalls != 1 { + t.Fatal(expectedError("persistent calls", 1, persistentCalls)) + } + b.Reset() + <-b.Trigger(1, nil) + if impersistentCalls != 1 { + t.Fatal(expectedError("impersistent calls", 1, impersistentCalls)) + } + if persistentCalls != 2 { + t.Fatal(expectedError("persistent calls", 2, persistentCalls)) + } + b.ClearPersistentBindings() + b.Reset() + <-b.Trigger(1, nil) + if impersistentCalls != 1 { + t.Fatal(expectedError("impersistent calls", 1, impersistentCalls)) + } + if persistentCalls != 2 { + t.Fatal(expectedError("persistent calls", 2, persistentCalls)) + } + }) +} + +func TestBus_EnterLoop(t *testing.T) { + t.Run("Basic", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + var calls int32 + b1 := b.UnsafeBind(event.Enter.UnsafeEventID, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + atomic.AddInt32(&calls, 1) + return 0 + }) + <-b1.Bound + cancel := event.EnterLoop(b, 50*time.Millisecond) + time.Sleep(1 * time.Second) + cancel() + if calls != 20 { + t.Fatal(expectedError("calls", 20, calls)) + } + }) +} diff --git a/event/caller.go b/event/caller.go index 1be9ac64..880fd1a7 100644 --- a/event/caller.go +++ b/event/caller.go @@ -2,7 +2,6 @@ package event import ( "sync" - "sync/atomic" ) // A CallerID is a caller ID that Callers use to bind themselves to receive callback @@ -26,7 +25,7 @@ type Caller interface { // This is an alternative to passing in the entity via closure scoping, // and allows for more general bindings as simple top level functions. type CallerMap struct { - highestID *int64 + highestID CallerID callersLock sync.RWMutex callers map[CallerID]Caller } @@ -35,8 +34,7 @@ type CallerMap struct { // is not valid for use if not created via this function. func NewCallerMap() *CallerMap { return &CallerMap{ - highestID: new(int64), - callers: map[CallerID]Caller{}, + callers: map[CallerID]Caller{}, } } @@ -44,14 +42,26 @@ func NewCallerMap() *CallerMap { // and returns it, after adding the given entity to // the caller map. func (cm *CallerMap) Register(e Caller) CallerID { - nextID := atomic.AddInt64(cm.highestID, 1) cm.callersLock.Lock() - cm.callers[CallerID(nextID)] = e - cm.callersLock.Unlock() - return CallerID(nextID) + defer cm.callersLock.Unlock() + // Q: Why not use atomic? + // A: We're in a mutex and therefore it is not needed. + // A2: We need the mutex to safely assign to the map. + // A3: We cannot atomically increment outside of the map, consider: + // - GR1 calls Clear, waits on Lock + // - GR2 calls Register, gets id 100, waits on lock + // - GR1 claims lock, resets highestID to 0, exits + // - GR2 claims lock, inserts id 100 in the map + // - ... later, register silently overwrites entity 100, its + // bindings will now panic on a bad type assertion + // + // Increment before assigning to preserve Global == caller 0 + cm.highestID++ + cm.callers[cm.highestID] = e + return cm.highestID } -// GetEntity returns the entity corresponding to the given ID within +// Get returns the entity corresponding to the given ID within // the caller map. If no entity is found, it returns nil. func (cm *CallerMap) GetEntity(id CallerID) Caller { cm.callersLock.RLock() @@ -59,7 +69,7 @@ func (cm *CallerMap) GetEntity(id CallerID) Caller { return cm.callers[id] } -// HasEntity returns whether the given caller id is an initialized entity +// Has returns whether the given caller id is an initialized entity // within the caller map. func (cm *CallerMap) HasEntity(id CallerID) bool { cm.callersLock.RLock() @@ -68,8 +78,8 @@ func (cm *CallerMap) HasEntity(id CallerID) bool { return ok } -// DestroyEntity removes an entity from the caller map. -func (cm *CallerMap) DestroyEntity(id CallerID) { +// Remove removes an entity from the caller map. +func (cm *CallerMap) RemoveEntity(id CallerID) { cm.callersLock.Lock() delete(cm.callers, id) cm.callersLock.Unlock() @@ -78,7 +88,7 @@ func (cm *CallerMap) DestroyEntity(id CallerID) { // Clear clears the caller map to forget all registered callers. func (cm *CallerMap) Clear() { cm.callersLock.Lock() - *cm.highestID = 0 + cm.highestID = 0 cm.callers = map[CallerID]Caller{} cm.callersLock.Unlock() } diff --git a/event/caller_test.go b/event/caller_test.go new file mode 100644 index 00000000..10707660 --- /dev/null +++ b/event/caller_test.go @@ -0,0 +1,67 @@ +package event_test + +import ( + "math/rand" + "testing" + + "github.com/oakmound/oak/v3/event" +) + +func TestCallerID_CID(t *testing.T) { + t.Run("Identity", func(t *testing.T) { + c := event.CallerID(rand.Intn(100000)) + if c != c.CID() { + t.Fatalf("callerID did not match itself: was %v, got %v", c, c.CID()) + } + }) +} + +func TestNewCallerMap(t *testing.T) { + t.Run("NotNil", func(t *testing.T) { + m := event.NewCallerMap() + if m == nil { + t.Fatalf("created caller map was nil") + } + }) +} + +func TestCallerMap_Register(t *testing.T) { + t.Run("Basic", func(t *testing.T) { + m := event.NewCallerMap() + c1 := event.CallerID(rand.Intn(10000)) + id := m.Register(c1) + c2 := m.GetEntity(id) + if c2 != c1 { + t.Fatalf("unable to retrieve registered caller") + } + if !m.HasEntity(id) { + t.Fatalf("caller map does not have registered caller") + } + }) + t.Run("Remove", func(t *testing.T) { + m := event.NewCallerMap() + c1 := event.CallerID(rand.Intn(10000)) + id := m.Register(c1) + m.RemoveEntity(id) + c3 := m.GetEntity(id) + if c3 != nil { + t.Fatalf("get entity had registered caller after remove") + } + if m.HasEntity(id) { + t.Fatalf("caller map has registered caller after remove") + } + }) + t.Run("Clear", func(t *testing.T) { + m := event.NewCallerMap() + c1 := event.CallerID(rand.Intn(10000)) + id := m.Register(c1) + m.Clear() + c3 := m.GetEntity(id) + if c3 != nil { + t.Fatalf("get entity had registered caller after clear") + } + if m.HasEntity(id) { + t.Fatalf("caller map has registered caller after clear") + } + }) +} diff --git a/event/events.go b/event/events.go index e4c75f65..4a90f2a2 100644 --- a/event/events.go +++ b/event/events.go @@ -3,8 +3,6 @@ package event import ( "sync/atomic" "time" - - "github.com/oakmound/oak/v3/alg/intgeom" ) // An UnsafeEventID is a non-typed eventID. EventIDs are just these, with type information attached. @@ -28,9 +26,6 @@ func RegisterEvent[T any]() EventID[T] { } } -// NoPayload is an alias for the empty struct. -type NoPayload = struct{} - // EnterPayload is the payload sent down to Enter bindings type EnterPayload struct { FramesElapsed int @@ -41,12 +36,4 @@ type EnterPayload struct { var ( // Enter: the beginning of every logical frame. Enter = RegisterEvent[EnterPayload]() - // ViewportUpdate: Triggered when the position of of the viewport changes - ViewportUpdate = RegisterEvent[intgeom.Point2]() - // OnStop: Triggered when the engine is stopped. - OnStop = RegisterEvent[NoPayload]() - // FocusGain: Triggered when the window gains focus - FocusGain = RegisterEvent[NoPayload]() - // FocusLoss: Triggered when the window loses focus - FocusLoss = RegisterEvent[NoPayload]() ) diff --git a/event/handler.go b/event/handler.go index 40f2ac88..6d89d9c7 100644 --- a/event/handler.go +++ b/event/handler.go @@ -1,9 +1,5 @@ package event -import ( - "time" -) - var ( _ Handler = &Bus{} ) @@ -12,8 +8,6 @@ var ( // for use in oak internally, and thus the functions that need to be replaced // by alternative event handlers. type Handler interface { - EnterLoop(time.Duration) - Stop() error Reset() TriggerForCaller(cid CallerID, event UnsafeEventID, data interface{}) chan struct{} Trigger(event UnsafeEventID, data interface{}) chan struct{} diff --git a/event/internal.go b/event/internal.go index 55e3995e..ea692834 100644 --- a/event/internal.go +++ b/event/internal.go @@ -1,6 +1,8 @@ package event -import "sync" +import ( + "sync" +) type bindableList map[BindID]UnsafeBindable @@ -29,12 +31,14 @@ func (bus *Bus) trigger(binds bindableList, eventID UnsafeEventID, callerID Call if callerID == Global || bus.callerMap.HasEntity(callerID) { response := bnd(callerID, bus, data) switch response { - case UnbindThis: + case ResponseUnbindThisBinding: // Q: Why does this call bus.Unbind when it already has the event index to delete? // A: This goroutine does not own a write lock on the bus, and should therefore // not modify its contents. We do not have a simple way of promoting our read lock // to a write lock. bus.Unbind(Binding{EventID: eventID, CallerID: callerID, BindID: bindID}) + case ResponseUnbindThisCaller: + bus.UnbindAllFrom(callerID) } } wg.Done() diff --git a/event/response.go b/event/response.go index f3fabcff..a4391b4a 100644 --- a/event/response.go +++ b/event/response.go @@ -4,11 +4,13 @@ type Response uint8 // Response types for bindables const ( - // NoResponse or 0, is returned by events that + // ResponseNone or 0, is returned by events that // don't want the event bus to do anything with // the event after they have been evaluated. This // is the usual behavior. - NoResponse Response = iota - // UnbindThis unbinds the one binding that returns it. - UnbindThis + ResponseNone Response = iota + // ResponseUnbindThisBinding unbinds the one binding that returns it. + ResponseUnbindThisBinding + // ResponseUnbindThisCaller unbinds all of a caller's bindings when returned from any binding. + ResponseUnbindThisCaller ) diff --git a/event/response_test.go b/event/response_test.go new file mode 100644 index 00000000..5a614558 --- /dev/null +++ b/event/response_test.go @@ -0,0 +1,9 @@ +package event_test + +import ( + "testing" +) + +func TestBindingResponses(t *testing.T) { + t.Skip("TODO") +} \ No newline at end of file diff --git a/event/trigger_test.go b/event/trigger_test.go index fd3cf0c6..2dae1215 100644 --- a/event/trigger_test.go +++ b/event/trigger_test.go @@ -43,7 +43,6 @@ func TestBus_TriggerForCaller(t *testing.T) { } return 0 }) - _ = binding select { case <-time.After(50 * time.Millisecond): t.Fatal("timeout waiting for bind to close channel") diff --git a/inputLoop.go b/inputLoop.go index 04115818..0e354b01 100644 --- a/inputLoop.go +++ b/inputLoop.go @@ -1,6 +1,7 @@ package oak import ( + "github.com/oakmound/oak/v3/alg/intgeom" "github.com/oakmound/oak/v3/event" "github.com/oakmound/oak/v3/timing" @@ -13,6 +14,17 @@ import ( "golang.org/x/mobile/event/size" ) +var ( + // ViewportUpdate: Triggered when the position of of the viewport changes + ViewportUpdate = event.RegisterEvent[intgeom.Point2]() + // OnStop: Triggered when the engine is stopped. + OnStop = event.RegisterEvent[struct{}]() + // FocusGain: Triggered when the window gains focus + FocusGain = event.RegisterEvent[struct{}]() + // FocusLoss: Triggered when the window loses focus + FocusLoss = event.RegisterEvent[struct{}]() +) + func (w *Window) inputLoop() { for { switch e := w.windowControl.NextEvent().(type) { @@ -21,25 +33,25 @@ func (w *Window) inputLoop() { switch e.To { case lifecycle.StageDead: dlog.Info(dlog.WindowClosed) - <-event.TriggerOn(w.eventHandler, event.OnStop, event.NoPayload{}) + <-event.TriggerOn(w.eventHandler, OnStop, struct{}{}) close(w.quitCh) return case lifecycle.StageFocused: w.inFocus = true // If you are in focused state, we don't care how you got there w.DrawTicker.Reset(timing.FPSToFrameDelay(w.DrawFrameRate)) - event.TriggerOn(w.eventHandler, event.FocusGain, event.NoPayload{}) + event.TriggerOn(w.eventHandler, FocusGain, struct{}{}) case lifecycle.StageVisible: // If the last state was focused, this means the app is out of focus // otherwise, we're visible for the first time if e.From > e.To { w.inFocus = false w.DrawTicker.Reset(timing.FPSToFrameDelay(w.IdleDrawFrameRate)) - event.TriggerOn(w.eventHandler, event.FocusLoss, event.NoPayload{}) + event.TriggerOn(w.eventHandler, FocusLoss, struct{}{}) } else { w.inFocus = true w.DrawTicker.Reset(timing.FPSToFrameDelay(w.DrawFrameRate)) - event.TriggerOn(w.eventHandler, event.FocusGain, event.NoPayload{}) + event.TriggerOn(w.eventHandler, FocusGain, struct{}{}) } } // Send key events diff --git a/inputTracker.go b/inputTracker.go index 0bf6b4c2..067b2455 100644 --- a/inputTracker.go +++ b/inputTracker.go @@ -17,7 +17,7 @@ type InputType int32 // InputChange is triggered when the most recent input device changes (e.g. keyboard to joystick or vice versa) var InputChange = event.RegisterEvent[InputType]() -var trackingJoystickChange = event.RegisterEvent[event.NoPayload]() +var trackingJoystickChange = event.RegisterEvent[struct{}]() // Supported Input Types const ( @@ -41,7 +41,7 @@ func (w *Window) trackInputChanges() { } return 0 }) - event.GlobalBind(w.eventHandler, trackingJoystickChange, func(event.NoPayload) event.Response { + event.GlobalBind(w.eventHandler, trackingJoystickChange, func(struct{}) event.Response { old := atomic.SwapInt32(&w.mostRecentInput, int32(InputMouse)) if InputType(old) != InputJoystick { event.TriggerOn(w.eventHandler, InputChange, InputJoystick) @@ -55,7 +55,7 @@ type joyHandler struct { } func (jh *joyHandler) Trigger(eventID event.UnsafeEventID, data interface{}) chan struct{} { - jh.handler.Trigger(trackingJoystickChange.UnsafeEventID, event.NoPayload{}) + jh.handler.Trigger(trackingJoystickChange.UnsafeEventID, struct{}{}) ch := make(chan struct{}) close(ch) return ch diff --git a/mouse/onCollision.go b/mouse/onCollision.go index 1190a1b3..7c77abaf 100644 --- a/mouse/onCollision.go +++ b/mouse/onCollision.go @@ -53,7 +53,7 @@ var ( func phaseCollisionEnter(id event.CallerID, handler event.Handler, _ interface{}) event.Response { e, ok := handler.GetCallerMap().GetEntity(id).(collisionPhase) if !ok { - return event.UnbindThis + return event.ResponseUnbindThisBinding } oc := e.getCollisionPhase() if oc == nil || oc.OnCollisionS == nil { diff --git a/render/particle/source.go b/render/particle/source.go index 6dab9391..c06fe445 100644 --- a/render/particle/source.go +++ b/render/particle/source.go @@ -205,9 +205,9 @@ func clearParticles(ps *Source, _ event.EnterPayload) event.Response { ps.EndFunc() } // TODO: not default - event.DefaultCallerMap.DestroyEntity(ps.CID()) + event.DefaultCallerMap.RemoveEntity(ps.CID()) ps.Deallocate(ps.pIDBlock) - return event.UnbindThis + return event.ResponseUnbindThisBinding } } return 0 diff --git a/render/particle/source_test.go b/render/particle/source_test.go index b9bb440f..728d68d5 100644 --- a/render/particle/source_test.go +++ b/render/particle/source_test.go @@ -50,7 +50,7 @@ func TestSource(t *testing.T) { for i := 0; i < 1000; i++ { rotateParticles(src, event.EnterPayload{}) } - for clearParticles(src, event.EnterPayload{}) != event.UnbindThis { + for clearParticles(src, event.EnterPayload{}) != event.ResponseUnbindThisBinding { } if !ended { diff --git a/render/sequence.go b/render/sequence.go index ef8731b5..0ce1afad 100644 --- a/render/sequence.go +++ b/render/sequence.go @@ -68,7 +68,7 @@ func (sq *Sequence) Copy() Modifiable { return newSq } -var AnimationEnd = event.RegisterEvent[event.NoPayload]() +var AnimationEnd = event.RegisterEvent[struct{}]() // SetTriggerID sets the ID that AnimationEnd will be triggered on when this // sequence loops over from its last frame to its first @@ -82,7 +82,7 @@ func (sq *Sequence) update() { sq.sheetPos = (sq.sheetPos + 1) % len(sq.rs) if sq.sheetPos == (len(sq.rs)-1) && sq.CallerID != 0 { // TODO: not default bus - event.TriggerForCallerOn(event.DefaultBus, sq.CallerID, AnimationEnd, event.NoPayload{}) + event.TriggerForCallerOn(event.DefaultBus, sq.CallerID, AnimationEnd, struct{}{}) } } } diff --git a/render/sequence_test.go b/render/sequence_test.go index ceb16cc7..45b56f29 100644 --- a/render/sequence_test.go +++ b/render/sequence_test.go @@ -24,7 +24,7 @@ func TestSequenceTrigger(t *testing.T) { d.CallerID = event.DefaultCallerMap.Register(d) sq.SetTriggerID(d.CallerID) triggerCh := make(chan struct{}) - event.Bind(event.DefaultBus, AnimationEnd, d, func(_ Dummy, _ event.NoPayload) event.Response { + event.Bind(event.DefaultBus, AnimationEnd, d, func(_ Dummy, _ struct{}) event.Response { triggerCh <- struct{}{} return 0 }) diff --git a/sceneLoop.go b/sceneLoop.go index 359d17cb..2d5e4bb1 100644 --- a/sceneLoop.go +++ b/sceneLoop.go @@ -5,6 +5,7 @@ import ( "github.com/oakmound/oak/v3/alg/intgeom" "github.com/oakmound/oak/v3/dlog" + "github.com/oakmound/oak/v3/event" "github.com/oakmound/oak/v3/oakerr" "github.com/oakmound/oak/v3/scene" "github.com/oakmound/oak/v3/timing" @@ -95,7 +96,7 @@ func (w *Window) sceneLoop(first string, trackingInputs, batchLoad bool) { dlog.Info(dlog.SceneLooping) cont := true - w.eventHandler.EnterLoop(timing.FPSToFrameDelay(w.FrameRate)) + enterCancel := event.EnterLoop(w.eventHandler, timing.FPSToFrameDelay(w.FrameRate)) nextSceneOverride := "" @@ -113,7 +114,7 @@ func (w *Window) sceneLoop(first string, trackingInputs, batchLoad bool) { dlog.Info(dlog.SceneEnding, w.SceneMap.CurrentScene) // We don't want enterFrames going off between scenes - dlog.ErrorCheck(w.eventHandler.Stop()) + enterCancel() prevScene = w.SceneMap.CurrentScene // Send a signal to stop drawing diff --git a/viewport.go b/viewport.go index 9df73069..8ad51ea4 100644 --- a/viewport.go +++ b/viewport.go @@ -34,7 +34,7 @@ func (w *Window) setViewport(pt intgeom.Point2) { } else { w.viewPos = pt } - event.TriggerOn(w.eventHandler, event.ViewportUpdate, w.viewPos) + event.TriggerOn(w.eventHandler, ViewportUpdate, w.viewPos) } // GetViewportBounds reports what bounds the viewport has been set to, if any. From ea990b1f2008de45e5197295ffdeee7559088780 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Thu, 31 Mar 2022 07:44:02 -0500 Subject: [PATCH 23/41] event: restore 100% test coverage --- event/bind.go | 17 +++++++++---- event/bind_test.go | 57 +++++++++++++++++++++++++++++++++++++++--- event/handler.go | 4 +-- event/response_test.go | 54 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 118 insertions(+), 14 deletions(-) diff --git a/event/bind.go b/event/bind.go index 38c31010..11fa7d63 100644 --- a/event/bind.go +++ b/event/bind.go @@ -33,8 +33,8 @@ type Binding struct { // Unbind unbinds the callback associated with this binding from it's own event handler. If this binding // does not belong to its handler or has already been unbound, this will do nothing. -func (b Binding) Unbind() { - b.Handler.Unbind(b) +func (b Binding) Unbind() chan struct{} { + return b.Handler.Unbind(b) } // A BindID is a unique identifier for a binding within a bus. @@ -71,7 +71,8 @@ func (bus *Bus) UnsafeBind(eventID UnsafeEventID, callerID CallerID, fn UnsafeBi // PersistentBind acts like UnsafeBind, but cause Bind to be called with these inputs after a Bus is Reset, i.e. // persisting the binding through bus resets. Unbinding this will not stop it from being rebound on the next -// Bus Reset-- ClearPersistentBindings will. +// Bus Reset-- ClearPersistentBindings will. If called concurrently during a bus Reset, the request may not be +// bound until the next bus Reset. func (bus *Bus) PersistentBind(eventID UnsafeEventID, callerID CallerID, fn UnsafeBindable) Binding { binding := bus.UnsafeBind(eventID, callerID, fn) go func() { @@ -88,7 +89,8 @@ func (bus *Bus) PersistentBind(eventID UnsafeEventID, callerID CallerID, fn Unsa // Unbind unregisters a binding from a bus concurrently. Once complete, triggers that would // have previously caused the Bindable callback to execute will no longer do so. -func (bus *Bus) Unbind(loc Binding) { +func (bus *Bus) Unbind(loc Binding) chan struct{} { + ch := make(chan struct{}) go func() { bus.mutex.Lock() defer bus.mutex.Unlock() @@ -98,7 +100,9 @@ func (bus *Bus) Unbind(loc Binding) { } l := bus.getBindableList(loc.EventID, loc.CallerID) delete(l, loc.BindID) + close(ch) }() + return ch } // A Bindable is a strongly typed callback function to be executed on Trigger. It must be paired @@ -132,12 +136,15 @@ func GlobalBind[Payload any](h Handler, ev EventID[Payload], fn GlobalBindable[P type UnsafeBindable func(CallerID, Handler, interface{}) Response // UnbindAllFrom unbinds all bindings currently bound to the provided caller via ID. -func (bus *Bus) UnbindAllFrom(c CallerID) { +func (bus *Bus) UnbindAllFrom(c CallerID) chan struct{} { + ch := make(chan struct{}) go func() { bus.mutex.Lock() for _, callerMap := range bus.bindingMap { delete(callerMap, c) } bus.mutex.Unlock() + close(ch) }() + return ch } diff --git a/event/bind_test.go b/event/bind_test.go index fc96108e..4fe1d942 100644 --- a/event/bind_test.go +++ b/event/bind_test.go @@ -45,7 +45,7 @@ func TestBus_Unbind(t *testing.T) { }) <-b2.Bound <-b.Trigger(1, nil) - b2.Unbind() + <-b2.Unbind() } if goodCalls != 1000 { t.Fatal("a pre-reset unbind unbound a post-reset binding", goodCalls) @@ -55,16 +55,65 @@ func TestBus_Unbind(t *testing.T) { func TestBind(t *testing.T) { t.Run("SuperficialCoverage", func(t *testing.T) { - t.Skip("TODO") + b := event.NewBus(event.NewCallerMap()) + var cid event.CallerID + id := b.GetCallerMap().Register(cid) + var calls int32 + b1 := event.Bind(b, event.Enter, id, func(event.CallerID, event.EnterPayload) event.Response { + atomic.AddInt32(&calls, 1) + return 0 + }) + <-b1.Bound + <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) + if calls != 1 { + t.Fatal(expectedError("calls", 1, calls)) + } }) } func TestGlobalBind(t *testing.T) { t.Run("SuperficialCoverage", func(t *testing.T) { - t.Skip("TODO") + b := event.NewBus(event.NewCallerMap()) + var calls int32 + b1 := event.GlobalBind(b, event.Enter, func(event.EnterPayload) event.Response { + atomic.AddInt32(&calls, 1) + return 0 + }) + <-b1.Bound + <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) + if calls != 1 { + t.Fatal(expectedError("calls", 1, calls)) + } }) } func TestBus_UnbindAllFrom(t *testing.T) { - t.Skip("TODO") + t.Run("Basic", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + var cid event.CallerID + id := b.GetCallerMap().Register(cid) + var calls int32 + for i := 0; i < 5; i++ { + b1 := event.Bind(b, event.Enter, id, func(event.CallerID, event.EnterPayload) event.Response { + atomic.AddInt32(&calls, 1) + return 0 + }) + <-b1.Bound + } + id2 := b.GetCallerMap().Register(cid) + b1 := event.Bind(b, event.Enter, id2, func(event.CallerID, event.EnterPayload) event.Response { + atomic.AddInt32(&calls, 1) + return 0 + }) + <-b1.Bound + <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) + if calls != 6 { + t.Fatal(expectedError("calls", 1, calls)) + } + <-b.UnbindAllFrom(id) + <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) + if calls != 7 { + t.Fatal(expectedError("calls", 1, calls)) + } + }) } diff --git a/event/handler.go b/event/handler.go index 6d89d9c7..af59e3ea 100644 --- a/event/handler.go +++ b/event/handler.go @@ -12,8 +12,8 @@ type Handler interface { TriggerForCaller(cid CallerID, event UnsafeEventID, data interface{}) chan struct{} Trigger(event UnsafeEventID, data interface{}) chan struct{} UnsafeBind(UnsafeEventID, CallerID, UnsafeBindable) Binding - Unbind(Binding) - UnbindAllFrom(CallerID) + Unbind(Binding) chan struct{} + UnbindAllFrom(CallerID) chan struct{} SetCallerMap(*CallerMap) GetCallerMap() *CallerMap } diff --git a/event/response_test.go b/event/response_test.go index 5a614558..7064e353 100644 --- a/event/response_test.go +++ b/event/response_test.go @@ -1,9 +1,57 @@ package event_test - + import ( + "sync/atomic" "testing" + "time" + + "github.com/oakmound/oak/v3/event" ) func TestBindingResponses(t *testing.T) { - t.Skip("TODO") -} \ No newline at end of file + t.Run("UnbindThisBinding", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + + var calls int32 + b1 := b.UnsafeBind(1, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + atomic.AddInt32(&calls, 1) + return event.ResponseUnbindThisBinding + }) + <-b1.Bound + <-b.Trigger(1, nil) + if calls != 1 { + t.Fatal(expectedError("calls", 1, calls)) + } + // we do not get a signal for when this unbinding is finished + time.Sleep(1 * time.Second) + <-b.Trigger(1, nil) + if calls != 1 { + t.Fatal(expectedError("calls", 1, calls)) + } + }) + t.Run("UNbindThisCaller", func(t *testing.T) { + b := event.NewBus(event.NewCallerMap()) + + var calls int32 + b1 := b.UnsafeBind(1, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + atomic.AddInt32(&calls, 1) + return event.ResponseUnbindThisCaller + }) + <-b1.Bound + b2 := b.UnsafeBind(1, 0, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + atomic.AddInt32(&calls, 1) + return 0 + }) + <-b2.Bound + <-b.Trigger(1, nil) + if calls != 2 { + t.Fatal(expectedError("calls", 1, calls)) + } + // we do not get a signal for when this unbinding is finished + time.Sleep(1 * time.Second) + <-b.Trigger(1, nil) + if calls != 2 { + t.Fatal(expectedError("calls", 2, calls)) + } + }) +} From 9d98d49db41e907360d95ba969393899fe52f661 Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 10:45:19 -0400 Subject: [PATCH 24/41] render/particle: Source CallerID ordering fix + Start adding non-defaults to sources --- render/particle/allocator_test.go | 2 +- render/particle/colorGenerator.go | 2 +- render/particle/gradientGenerator.go | 2 +- render/particle/source.go | 17 ++++++++++++----- render/particle/spriteGenerator.go | 2 +- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/render/particle/allocator_test.go b/render/particle/allocator_test.go index 31c73f59..bbf04497 100644 --- a/render/particle/allocator_test.go +++ b/render/particle/allocator_test.go @@ -32,7 +32,7 @@ func TestAllocatorLookup(t *testing.T) { a := NewAllocator() go a.Run() - src := NewSource(NewColorGenerator(), 0) + src := NewDefaultSource(NewColorGenerator(), 0) cid := src.CID() pidBlock := a.Allocate(cid) src2 := a.LookupSource(pidBlock * blockSize) diff --git a/render/particle/colorGenerator.go b/render/particle/colorGenerator.go index 4a7a7c32..4b22ceeb 100644 --- a/render/particle/colorGenerator.go +++ b/render/particle/colorGenerator.go @@ -51,7 +51,7 @@ func (cg *ColorGenerator) Generate(layer int) *Source { if cg.Rotation != nil { cg.Rotation = cg.Rotation.Mult(alg.DegToRad) } - return NewSource(cg, layer) + return NewDefaultSource(cg, layer) } // GenerateParticle creates a particle from a generator diff --git a/render/particle/gradientGenerator.go b/render/particle/gradientGenerator.go index d2a947d7..405e3cac 100644 --- a/render/particle/gradientGenerator.go +++ b/render/particle/gradientGenerator.go @@ -45,7 +45,7 @@ func (gg *GradientGenerator) Generate(layer int) *Source { if gg.Rotation != nil { gg.Rotation = gg.Rotation.Mult(alg.DegToRad) } - return NewSource(gg, layer) + return NewDefaultSource(gg, layer) } // GenerateParticle creates a particle from a generator diff --git a/render/particle/source.go b/render/particle/source.go index 6dab9391..f442483d 100644 --- a/render/particle/source.go +++ b/render/particle/source.go @@ -33,22 +33,29 @@ type Source struct { stopped bool } -// NewSource creates a new source -func NewSource(g Generator, stackLevel int) *Source { +// NewDefaultSource creates a new sourceattached to the default event bus. +func NewDefaultSource(g Generator, stackLevel int) *Source { + return NewSource(event.DefaultBus, g, stackLevel) +} + +// NewSource for particles constructed from a generator with specifications on how the particles should be handled. +func NewSource(handler event.Handler, g Generator, stackLevel int) *Source { ps := new(Source) ps.Generator = g ps.stackLevel = stackLevel ps.Allocator = DefaultAllocator - cid := event.DefaultCallerMap.Register(ps) + cid := handler.GetCallerMap().Register(ps) ps.stopRotateAt = time.Now().Add( time.Duration(ps.Generator.GetBaseGenerator().Duration.Poll()) * time.Millisecond) - ps.rotateBinding = event.Bind(event.DefaultBus, event.Enter, ps, rotateParticles) - ps.CallerID = cid + ps.CallerID = cid // cid must be set before the following bind call + ps.rotateBinding = event.Bind(handler, event.Enter, ps, rotateParticles) + ps.pIDBlock = ps.Allocate(ps.CallerID) return ps } +// CID of our particle source func (ps *Source) CID() event.CallerID { return ps.CallerID } diff --git a/render/particle/spriteGenerator.go b/render/particle/spriteGenerator.go index 1cd5c760..111b6102 100644 --- a/render/particle/spriteGenerator.go +++ b/render/particle/spriteGenerator.go @@ -37,7 +37,7 @@ func (sg *SpriteGenerator) Generate(layer int) *Source { if sg.Rotation != nil { sg.Rotation = sg.Rotation.Mult(alg.DegToRad) } - return NewSource(sg, layer) + return NewDefaultSource(sg, layer) } // GenerateParticle creates a particle from a generator From d0fba73b85e68b985a420b11d28c9859ec44e511 Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 10:46:14 -0400 Subject: [PATCH 25/41] key: Pull in more mobile key info into oak for cleaner calling signatures --- inputLoop.go | 6 +- key/events.go | 3 +- key/keycodes.go | 139 ++++++++++++++ key/keys.go | 486 ++++++++++++++++++++++++------------------------ 4 files changed, 387 insertions(+), 247 deletions(-) create mode 100644 key/keycodes.go diff --git a/inputLoop.go b/inputLoop.go index 04115818..62d97b50 100644 --- a/inputLoop.go +++ b/inputLoop.go @@ -103,7 +103,7 @@ func (w *Window) TriggerKeyDown(e okey.Event) { k := e.Code.String()[4:] w.SetDown(k) event.TriggerOn(w.eventHandler, okey.AnyDown, e) - event.TriggerOn(w.eventHandler, okey.Down(e.Code), e) + event.TriggerOn(w.eventHandler, okey.Down(okey.Code(e.Code)), e) } // TriggerKeyUp triggers a software-emulated key release. @@ -114,7 +114,7 @@ func (w *Window) TriggerKeyUp(e okey.Event) { k := e.Code.String()[4:] w.SetUp(k) event.TriggerOn(w.eventHandler, okey.AnyUp, e) - event.TriggerOn(w.eventHandler, okey.Up(e.Code), e) + event.TriggerOn(w.eventHandler, okey.Up(okey.Code(e.Code)), e) } // TriggerKeyHeld triggers a software-emulated key hold signal. @@ -123,7 +123,7 @@ func (w *Window) TriggerKeyUp(e okey.Event) { // from a real key hold signal. func (w *Window) TriggerKeyHeld(e okey.Event) { event.TriggerOn(w.eventHandler, okey.AnyHeld, e) - event.TriggerOn(w.eventHandler, okey.Held(e.Code), e) + event.TriggerOn(w.eventHandler, okey.Held(okey.Code(e.Code)), e) } // TriggerMouseEvent triggers a software-emulated mouse event. diff --git a/key/events.go b/key/events.go index 12e2e62b..54358f3c 100644 --- a/key/events.go +++ b/key/events.go @@ -23,11 +23,12 @@ var ( type Event = key.Event // A code is a unique integer code for a given common key -type Code = key.Code +const CodeA key.Code = key.Code(A) var upEventsLock sync.Mutex var upEvents = map[Code]event.EventID[Event]{} +// Up checks func Up(code Code) event.EventID[Event] { upEventsLock.Lock() defer upEventsLock.Unlock() diff --git a/key/keycodes.go b/key/keycodes.go new file mode 100644 index 00000000..8c3ec701 --- /dev/null +++ b/key/keycodes.go @@ -0,0 +1,139 @@ +package key + +// Code is the identity of a key relative to a notional "standard" keyboard. +// It is a straight copy of mobile package's key codes cleaned up for ease of binding in oak. +// See AllKeys for string mappers. +type Code uint32 + +const ( + Unknown Code = 0 + A Code = 4 + B Code = 5 + C Code = 6 + D Code = 7 + E Code = 8 + F Code = 9 + G Code = 10 + H Code = 11 + I Code = 12 + J Code = 13 + K Code = 14 + L Code = 15 + M Code = 16 + N Code = 17 + O Code = 18 + P Code = 19 + Q Code = 20 + R Code = 21 + S Code = 22 + T Code = 23 + U Code = 24 + V Code = 25 + W Code = 26 + X Code = 27 + Y Code = 28 + Z Code = 29 + + Num1 Code = 30 + Num2 Code = 31 + Num3 Code = 32 + Num4 Code = 33 + Num5 Code = 34 + Num6 Code = 35 + Num7 Code = 36 + Num8 Code = 37 + Num9 Code = 38 + Num0 Code = 39 + + ReturnEnter Code = 40 + Escape Code = 41 + DeleteBackspace Code = 42 + Tab Code = 43 + Spacebar Code = 44 + HyphenMinus Code = 45 + EqualSign Code = 46 + LeftSquareBracket Code = 47 + RightSquareBracket Code = 48 + Backslash Code = 49 + Semicolon Code = 51 + Apostrophe Code = 52 + GraveAccent Code = 53 + Comma Code = 54 + FullStop Code = 55 + Slash Code = 56 + CapsLock Code = 57 + + F1 Code = 58 + F2 Code = 59 + F3 Code = 60 + F4 Code = 61 + F5 Code = 62 + F6 Code = 63 + F7 Code = 64 + F8 Code = 65 + F9 Code = 66 + F10 Code = 67 + F11 Code = 68 + F12 Code = 69 + + Pause Code = 72 + Insert Code = 73 + Home Code = 74 + PageUp Code = 75 + DeleteForward Code = 76 + End Code = 77 + PageDown Code = 78 + + RightArrow Code = 79 + LeftArrow Code = 80 + DownArrow Code = 81 + UpArrow Code = 82 + + KeypadNumLock Code = 83 + KeypadSlash Code = 84 + KeypadAsterisk Code = 85 + KeypadHyphenMinus Code = 86 + KeypadPlusSign Code = 87 + KeypadEnter Code = 88 + Keypad1 Code = 89 + Keypad2 Code = 90 + Keypad3 Code = 91 + Keypad4 Code = 92 + Keypad5 Code = 93 + Keypad6 Code = 94 + Keypad7 Code = 95 + Keypad8 Code = 96 + Keypad9 Code = 97 + Keypad0 Code = 98 + KeypadFullStop Code = 99 + KeypadEqualSign Code = 103 + + F13 Code = 104 + F14 Code = 105 + F15 Code = 106 + F16 Code = 107 + F17 Code = 108 + F18 Code = 109 + F19 Code = 110 + F20 Code = 111 + F21 Code = 112 + F22 Code = 113 + F23 Code = 114 + F24 Code = 115 + + Help Code = 117 + + Mute Code = 127 + VolumeUp Code = 128 + VolumeDown Code = 129 + + LeftControl Code = 224 + LeftShift Code = 225 + LeftAlt Code = 226 + LeftGUI Code = 227 + RightControl Code = 228 + RightShift Code = 229 + RightAlt Code = 230 + RightGUI Code = 231 + Compose Code = 0x10000 +) diff --git a/key/keys.go b/key/keys.go index 1cb73fc9..d3c821c0 100644 --- a/key/keys.go +++ b/key/keys.go @@ -7,271 +7,271 @@ package key // These strings are sent as payloads to Key.Down and Key.Up events, // and through "KeyDown"+$a, "KeyUp"+$a for any $a in the const. const ( - Unknown = "Unknown" + UnknownStr = "Unknown" - A = "A" - B = "B" - C = "C" - D = "D" - E = "E" - F = "F" - G = "G" - H = "H" - I = "I" - J = "J" - K = "K" - L = "L" - M = "M" - N = "N" - O = "O" - P = "P" - Q = "Q" - R = "R" - S = "S" - T = "T" - U = "U" - V = "V" - W = "W" - X = "X" - Y = "Y" - Z = "Z" + AStr = "A" + BStr = "B" + CStr = "C" + DStr = "D" + EStr = "E" + FStr = "F" + GStr = "G" + HStr = "H" + IStr = "I" + JStr = "J" + KStr = "K" + LStr = "L" + MStr = "M" + NStr = "N" + OStr = "O" + PStr = "P" + QStr = "Q" + RStr = "R" + SStr = "S" + TStr = "T" + UStr = "U" + VStr = "V" + WStr = "W" + XStr = "X" + YStr = "Y" + ZStr = "Z" - One = "1" - Two = "2" - Three = "3" - Four = "4" - Five = "5" - Six = "6" - Seven = "7" - Eight = "8" - Nine = "9" - Zero = "0" + OneStr = "1" + TwoStr = "2" + ThreeStr = "3" + FourStr = "4" + FiveStr = "5" + SixStr = "6" + SevenStr = "7" + EightStr = "8" + NineStr = "9" + ZeroStr = "0" - ReturnEnter = "ReturnEnter" - Enter = ReturnEnter - Escape = "Escape" - DeleteBackspace = "DeleteBackspace" - Tab = "Tab" - Spacebar = "Spacebar" - HyphenMinus = "HyphenMinus" //- - EqualSign = "EqualSign" //= - LeftSquareBracket = "LeftSquareBracket" //[ - RightSquareBracket = "RightSquareBracket" //] - Backslash = "Backslash" //\ - Semicolon = "Semicolon" //; - Apostrophe = "Apostrophe" //' - GraveAccent = "GraveAccent" //` - Comma = "Comma" //, - FullStop = "FullStop" //. - Period = FullStop - Slash = "Slash" /// - CapsLock = "CapsLock" + ReturnEnterStr = "ReturnEnter" + Enter = ReturnEnter + EscapeStr = "Escape" + DeleteBackspaceStr = "DeleteBackspace" + TabStr = "Tab" + SpacebarStr = "Spacebar" + HyphenMinusStr = "HyphenMinus" //- + EqualSignStr = "EqualSign" //= + LeftSquareBracketStr = "LeftSquareBracket" //[ + RightSquareBracketStr = "RightSquareBracket" //] + BackslashStr = "Backslash" //\ + SemicolonStr = "Semicolon" //; + ApostropheStr = "Apostrophe" //' + GraveAccentStr = "GraveAccent" //` + CommaStr = "Comma" //, + FullStopStr = "FullStop" //. + Period = "FullStop" + SlashStr = "Slash" /// + CapsLockStr = "CapsLock" - F1 = "F1" - F2 = "F2" - F3 = "F3" - F4 = "F4" - F5 = "F5" - F6 = "F6" - F7 = "F7" - F8 = "F8" - F9 = "F9" - F10 = "F10" - F11 = "F11" - F12 = "F12" + F1Str = "F1" + F2Str = "F2" + F3Str = "F3" + F4Str = "F4" + F5Str = "F5" + F6Str = "F6" + F7Str = "F7" + F8Str = "F8" + F9Str = "F9" + F10Str = "F10" + F11Str = "F11" + F12Str = "F12" - Pause = "Pause" - Insert = "Insert" - Home = "Home" - PageUp = "PageUp" - DeleteForward = "DeleteForward" - End = "End" - PageDown = "PageDown" + PauseStr = "Pause" + InsertStr = "Insert" + HomeStr = "Home" + PageUpStr = "PageUp" + DeleteForwardStr = "DeleteForward" + EndStr = "End" + PageDownStr = "PageDown" - RightArrow = "RightArrow" - LeftArrow = "LeftArrow" - DownArrow = "DownArrow" - UpArrow = "UpArrow" + RightArrowStr = "RightArrow" + LeftArrowStr = "LeftArrow" + DownArrowStr = "DownArrow" + UpArrowStr = "UpArrow" - KeypadNumLock = "KeypadNumLock" - KeypadSlash = "KeypadSlash" /// - KeypadAsterisk = "KeypadAsterisk" //* - KeypadHyphenMinus = "KeypadHyphenMinus" //- - KeypadPlusSign = "KeypadPlusSign" //+ - KeypadEnter = "KeypadEnter" - Keypad1 = "Keypad1" - Keypad2 = "Keypad2" - Keypad3 = "Keypad3" - Keypad4 = "Keypad4" - Keypad5 = "Keypad5" - Keypad6 = "Keypad6" - Keypad7 = "Keypad7" - Keypad8 = "Keypad8" - Keypad9 = "Keypad9" - Keypad0 = "Keypad0" - KeypadFullStop = "KeypadFullStop" //. - KeypadPeriod = KeypadFullStop - KeypadEqualSign = "KeypadEqualSign" //= + KeypadNumLockStr = "KeypadNumLock" + KeypadSlashStr = "KeypadSlash" /// + KeypadAsteriskStr = "KeypadAsterisk" //* + KeypadHyphenMinusStr = "KeypadHyphenMinus" //- + KeypadPlusSignStr = "KeypadPlusSign" //+ + KeypadEnterStr = "KeypadEnter" + Keypad1Str = "Keypad1" + Keypad2Str = "Keypad2" + Keypad3Str = "Keypad3" + Keypad4Str = "Keypad4" + Keypad5Str = "Keypad5" + Keypad6Str = "Keypad6" + Keypad7Str = "Keypad7" + Keypad8Str = "Keypad8" + Keypad9Str = "Keypad9" + Keypad0Str = "Keypad0" + KeypadFullStopStr = "KeypadFullStop" //. + KeypadPeriod = "KeypadFullStop" + KeypadEqualSignStr = "KeypadEqualSign" //= - F13 = "F13" - F14 = "F14" - F15 = "F15" - F16 = "F16" - F17 = "F17" - F18 = "F18" - F19 = "F19" - F20 = "F20" - F21 = "F21" - F22 = "F22" - F23 = "F23" - F24 = "F24" + F13Str = "F13" + F14Str = "F14" + F15Str = "F15" + F16Str = "F16" + F17Str = "F17" + F18Str = "F18" + F19Str = "F19" + F20Str = "F20" + F21Str = "F21" + F22Str = "F22" + F23Str = "F23" + F24Str = "F24" - Help = "Help" + HelpStr = "Help" - Mute = "Mute" - VolumeUp = "VolumeUp" - VolumeDown = "VolumeDown" + MuteStr = "Mute" + VolumeUpStr = "VolumeUp" + VolumeDownStr = "VolumeDown" - LeftControl = "LeftControl" - LeftShift = "LeftShift" - LeftAlt = "LeftAlt" - LeftGUI = "LeftGUI" - RightControl = "RightControl" - RightShift = "RightShift" - RightAlt = "RightAlt" - RightGUI = "RightGUI" + LeftControlStr = "LeftControl" + LeftShiftStr = "LeftShift" + LeftAltStr = "LeftAlt" + LeftGUIStr = "LeftGUI" + RightControlStr = "RightControl" + RightShiftStr = "RightShift" + RightAltStr = "RightAlt" + RightGUIStr = "RightGUI" ) -// AllKeys is the set of all defined key codes -var AllKeys = map[string]struct{}{ - Unknown: {}, +// AllKeys is the set of all defined key codes to their Codes +var AllKeys = map[string]Code{ + UnknownStr: Unknown, - A: {}, - B: {}, - C: {}, - D: {}, - E: {}, - F: {}, - G: {}, - H: {}, - I: {}, - J: {}, - K: {}, - L: {}, - M: {}, - N: {}, - O: {}, - P: {}, - Q: {}, - R: {}, - S: {}, - T: {}, - U: {}, - V: {}, - W: {}, - X: {}, - Y: {}, - Z: {}, + AStr: A, + BStr: B, + CStr: C, + DStr: D, + EStr: E, + FStr: F, + GStr: G, + HStr: H, + IStr: I, + JStr: J, + KStr: K, + LStr: L, + MStr: M, + NStr: N, + OStr: O, + PStr: P, + QStr: Q, + RStr: R, + SStr: S, + TStr: T, + UStr: U, + VStr: V, + WStr: W, + XStr: X, + YStr: Y, + ZStr: Z, - One: {}, - Two: {}, - Three: {}, - Four: {}, - Five: {}, - Six: {}, - Seven: {}, - Eight: {}, - Nine: {}, - Zero: {}, + OneStr: Num1, + TwoStr: Num2, + ThreeStr: Num3, + FourStr: Num4, + FiveStr: Num5, + SixStr: Num6, + SevenStr: Num7, + EightStr: Num8, + NineStr: Num9, + ZeroStr: Num0, - ReturnEnter: {}, - Escape: {}, - DeleteBackspace: {}, - Tab: {}, - Spacebar: {}, - HyphenMinus: {}, - EqualSign: {}, - LeftSquareBracket: {}, - RightSquareBracket: {}, - Backslash: {}, - Semicolon: {}, - Apostrophe: {}, - GraveAccent: {}, - Comma: {}, - FullStop: {}, - Slash: {}, - CapsLock: {}, + ReturnEnterStr: ReturnEnter, + EscapeStr: Escape, + DeleteBackspaceStr: DeleteBackspace, + TabStr: Tab, + SpacebarStr: Spacebar, + HyphenMinusStr: HyphenMinus, + EqualSignStr: EqualSign, + LeftSquareBracketStr: LeftSquareBracket, + RightSquareBracketStr: RightSquareBracket, + BackslashStr: Backslash, + SemicolonStr: Semicolon, + ApostropheStr: Apostrophe, + GraveAccentStr: GraveAccent, + CommaStr: Comma, + FullStopStr: FullStop, + SlashStr: Slash, + CapsLockStr: CapsLock, - F1: {}, - F2: {}, - F3: {}, - F4: {}, - F5: {}, - F6: {}, - F7: {}, - F8: {}, - F9: {}, - F10: {}, - F11: {}, - F12: {}, + F1Str: F1, + F2Str: F2, + F3Str: F3, + F4Str: F4, + F5Str: F5, + F6Str: F6, + F7Str: F7, + F8Str: F8, + F9Str: F9, + F10Str: F10, + F11Str: F11, + F12Str: F12, - Pause: {}, - Insert: {}, - Home: {}, - PageUp: {}, - DeleteForward: {}, - End: {}, - PageDown: {}, + PauseStr: Pause, + InsertStr: Insert, + HomeStr: Home, + PageUpStr: PageUp, + DeleteForwardStr: DeleteForward, + EndStr: End, + PageDownStr: PageDown, - RightArrow: {}, - LeftArrow: {}, - DownArrow: {}, - UpArrow: {}, + RightArrowStr: RightArrow, + LeftArrowStr: LeftArrow, + DownArrowStr: DownArrow, + UpArrowStr: UpArrow, - KeypadNumLock: {}, - KeypadSlash: {}, - KeypadAsterisk: {}, - KeypadHyphenMinus: {}, - KeypadPlusSign: {}, - KeypadEnter: {}, - Keypad1: {}, - Keypad2: {}, - Keypad3: {}, - Keypad4: {}, - Keypad5: {}, - Keypad6: {}, - Keypad7: {}, - Keypad8: {}, - Keypad9: {}, - Keypad0: {}, - KeypadFullStop: {}, - KeypadEqualSign: {}, + KeypadNumLockStr: KeypadNumLock, + KeypadSlashStr: KeypadSlash, + KeypadAsteriskStr: KeypadAsterisk, + KeypadHyphenMinusStr: KeypadHyphenMinus, + KeypadPlusSignStr: KeypadPlusSign, + KeypadEnterStr: KeypadEnter, + Keypad1Str: Keypad1, + Keypad2Str: Keypad2, + Keypad3Str: Keypad3, + Keypad4Str: Keypad4, + Keypad5Str: Keypad5, + Keypad6Str: Keypad6, + Keypad7Str: Keypad7, + Keypad8Str: Keypad8, + Keypad9Str: Keypad9, + Keypad0Str: Keypad0, + KeypadFullStopStr: KeypadFullStop, + KeypadEqualSignStr: KeypadEqualSign, - F13: {}, - F14: {}, - F15: {}, - F16: {}, - F17: {}, - F18: {}, - F19: {}, - F20: {}, - F21: {}, - F22: {}, - F23: {}, - F24: {}, + F13Str: F13, + F14Str: F14, + F15Str: F15, + F16Str: F16, + F17Str: F17, + F18Str: F18, + F19Str: F19, + F20Str: F20, + F21Str: F21, + F22Str: F22, + F23Str: F23, + F24Str: F24, - Help: {}, + HelpStr: Help, - Mute: {}, - VolumeUp: {}, - VolumeDown: {}, + MuteStr: Mute, + VolumeUpStr: VolumeUp, + VolumeDownStr: VolumeDown, - LeftControl: {}, - LeftShift: {}, - LeftAlt: {}, - LeftGUI: {}, - RightControl: {}, - RightShift: {}, - RightAlt: {}, - RightGUI: {}, + LeftControlStr: LeftControl, + LeftShiftStr: LeftShift, + LeftAltStr: LeftAlt, + LeftGUIStr: LeftGUI, + RightControlStr: RightControl, + RightShiftStr: RightShift, + RightAltStr: RightAlt, + RightGUIStr: RightGUI, } From 494c9e14db8c936bb57f90b309fe1396d4705f93 Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 11:04:47 -0400 Subject: [PATCH 26/41] debugtools\inputviz: Updates for the new key and cid changes --- debugtools/inputviz/joystick.go | 4 +--- debugtools/inputviz/keyboard.go | 12 ++++++------ debugtools/inputviz/mouse.go | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/debugtools/inputviz/joystick.go b/debugtools/inputviz/joystick.go index c2fcb46b..a1c0c16f 100644 --- a/debugtools/inputviz/joystick.go +++ b/debugtools/inputviz/joystick.go @@ -9,8 +9,6 @@ import ( "math" "time" - mkey "golang.org/x/mobile/event/key" - "github.com/oakmound/oak/v3/alg/floatgeom" "github.com/oakmound/oak/v3/dlog" "github.com/oakmound/oak/v3/event" @@ -189,7 +187,7 @@ func (j *Joystick) RenderAndListen(ctx *scene.Context, joy *joystick.Joystick, l }) // TODO: it is bad that you need to import two 'key' packages - b2 := event.Bind(ctx, key.Down(mkey.CodeSpacebar), j, func(j *Joystick, _ key.Event) event.Response { + b2 := event.Bind(ctx, key.Down(key.Spacebar), j, func(j *Joystick, _ key.Event) event.Response { j.joy.Vibrate(math.MaxUint16, math.MaxUint16) go func() { time.Sleep(1 * time.Second) diff --git a/debugtools/inputviz/keyboard.go b/debugtools/inputviz/keyboard.go index 52b24f46..e753f8af 100644 --- a/debugtools/inputviz/keyboard.go +++ b/debugtools/inputviz/keyboard.go @@ -85,12 +85,12 @@ func (l *LayoutQWERTY) init() { l.layoutMap = make(map[string]LayoutPosition) qwertyRows := [][]LayoutKey{ - {sk(key.Escape), gap(1), sk(key.F1), sk(key.F2), sk(key.F3), sk(key.F4), gap(.5), sk(key.F5), sk(key.F6), sk(key.F7), sk(key.F8), gap(.5), sk(key.F9), sk(key.F10), sk(key.F11), sk(key.F12), gap(2.1), sk(key.Pause)}, - {sk(key.GraveAccent), sk(key.One), sk(key.Two), sk(key.Three), sk(key.Four), sk(key.Five), sk(key.Six), sk(key.Seven), sk(key.Eight), sk(key.Nine), sk(key.Zero), sk(key.HyphenMinus), sk(key.EqualSign), wideKey{key.DeleteBackspace, 2.0}, gap(.1), sk(key.Insert), sk(key.Home), sk(key.PageUp), gap(.1), sk(key.KeypadNumLock), sk(key.KeypadSlash), sk(key.KeypadAsterisk), sk(key.KeypadHyphenMinus)}, - {wideKey{key.Tab, 1.5}, sk(key.Q), sk(key.W), sk(key.E), sk(key.R), sk(key.T), sk(key.Y), sk(key.U), sk(key.I), sk(key.O), sk(key.P), sk(key.LeftSquareBracket), sk(key.RightSquareBracket), wideKey{key.Backslash, 1.5}, gap(.1), sk(key.DeleteForward), sk(key.End), sk(key.PageDown), gap(.1), sk(key.Keypad7), sk(key.Keypad8), sk(key.Keypad9), tallKey{key.KeypadPlusSign, 2}}, - {wideKey{key.CapsLock, 1.5}, sk(key.A), sk(key.S), sk(key.D), sk(key.F), sk(key.G), sk(key.H), sk(key.J), sk(key.K), sk(key.L), sk(key.Semicolon), sk(key.Apostrophe), wideKey{key.ReturnEnter, 2.5}, gap(3.2), sk(key.Keypad4), sk(key.Keypad5), sk(key.Keypad6)}, - {wideKey{key.LeftShift, 2.0}, sk(key.Z), sk(key.X), sk(key.C), sk(key.V), sk(key.B), sk(key.N), sk(key.M), sk(key.Comma), sk(key.FullStop), sk(key.Slash), wideKey{key.RightShift, 3.0}, gap(1.1), sk(key.UpArrow), gap(1.1), sk(key.Keypad1), sk(key.Keypad2), sk(key.Keypad3), tallKey{key.KeypadEnter, 2.0}}, - {wideKey{key.LeftControl, 1.5}, sk(key.LeftGUI), wideKey{key.LeftAlt, 1.5}, wideKey{key.Spacebar, 7.0}, wideKey{key.RightAlt, 1.5}, sk(key.RightGUI), wideKey{key.RightControl, 1.5}, gap(.1), sk(key.LeftArrow), sk(key.DownArrow), sk(key.RightArrow), gap(.1), wideKey{key.Keypad0, 2.0}, sk(key.KeypadPeriod)}, + {sk(key.EscapeStr), gap(1), sk(key.F1Str), sk(key.F2Str), sk(key.F3Str), sk(key.F4Str), gap(.5), sk(key.F5Str), sk(key.F6Str), sk(key.F7Str), sk(key.F8Str), gap(.5), sk(key.F9Str), sk(key.F10Str), sk(key.F11Str), sk(key.F12Str), gap(2.1), sk(key.Pause)}, + {sk(key.GraveAccentStr), sk(key.OneStr), sk(key.TwoStr), sk(key.ThreeStr), sk(key.FourStr), sk(key.FiveStr), sk(key.SixStr), sk(key.SevenStr), sk(key.EightStr), sk(key.NineStr), sk(key.ZeroStr), sk(key.HyphenMinusStr), sk(key.EqualSignStr), wideKey{key.DeleteBackspaceStr, 2.0}, gap(.1), sk(key.InsertStr), sk(key.HomeStr), sk(key.PageUpStr), gap(.1), sk(key.KeypadNumLockStr), sk(key.KeypadSlashStr), sk(key.KeypadAsteriskStr), sk(key.KeypadHyphenMinus)}, + {wideKey{key.TabStr, 1.5}, sk(key.QStr), sk(key.WStr), sk(key.EStr), sk(key.RStr), sk(key.TStr), sk(key.YStr), sk(key.UStr), sk(key.IStr), sk(key.OStr), sk(key.PStr), sk(key.LeftSquareBracketStr), sk(key.RightSquareBracketStr), wideKey{key.BackslashStr, 1.5}, gap(.1), sk(key.DeleteForwardStr), sk(key.EndStr), sk(key.PageDownStr), gap(.1), sk(key.Keypad7Str), sk(key.Keypad8Str), sk(key.Keypad9Str), tallKey{key.KeypadPlusSignStr, 2}}, + {wideKey{key.CapsLockStr, 1.5}, sk(key.AStr), sk(key.SStr), sk(key.DStr), sk(key.FStr), sk(key.GStr), sk(key.HStr), sk(key.JStr), sk(key.KStr), sk(key.LStr), sk(key.SemicolonStr), sk(key.ApostropheStr), wideKey{key.ReturnEnterStr, 2.5}, gap(3.2), sk(key.Keypad4Str), sk(key.Keypad5Str), sk(key.Keypad6)}, + {wideKey{key.LeftShiftStr, 2.0}, sk(key.ZStr), sk(key.XStr), sk(key.CStr), sk(key.VStr), sk(key.BStr), sk(key.NStr), sk(key.MStr), sk(key.CommaStr), sk(key.FullStopStr), sk(key.SlashStr), wideKey{key.RightShiftStr, 3.0}, gap(1.1), sk(key.UpArrowStr), gap(1.1), sk(key.Keypad1Str), sk(key.Keypad2Str), sk(key.Keypad3Str), tallKey{key.KeypadEnterStr, 2.0}}, + {wideKey{key.LeftControlStr, 1.5}, sk(key.LeftGUIStr), wideKey{key.LeftAltStr, 1.5}, wideKey{key.SpacebarStr, 7.0}, wideKey{key.RightAltStr, 1.5}, sk(key.RightGUIStr), wideKey{key.RightControlStr, 1.5}, gap(.1), sk(key.LeftArrowStr), sk(key.DownArrowStr), sk(key.RightArrowStr), gap(.1), wideKey{key.Keypad0Str, 2.0}, sk(key.KeypadPeriod)}, } rowFloats := []float64{0.0, 1.1, 2.1, 3.1, 4.1, 5.1} for row, cols := range qwertyRows { diff --git a/debugtools/inputviz/mouse.go b/debugtools/inputviz/mouse.go index b0d2323c..fbaceb88 100644 --- a/debugtools/inputviz/mouse.go +++ b/debugtools/inputviz/mouse.go @@ -37,7 +37,7 @@ func (m *Mouse) CID() event.CallerID { func (m *Mouse) RenderAndListen(ctx *scene.Context, layer int) error { m.ctx = ctx - m.CallerID = ctx.Handler.GetCallerMap().Register(m) + m.CallerID = ctx.Register(m) if m.Rect.W() == 0 || m.Rect.H() == 0 { m.Rect.Max = m.Rect.Min.Add(floatgeom.Point2{60, 100}) From 780a1fb93b841b7ebe90c2301cdd73d9fd739f3a Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 11:05:21 -0400 Subject: [PATCH 27/41] entities: Moving works as expected post event refactor --- entities/doodad.go | 7 +++++++ entities/x/move/topdown.go | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/entities/doodad.go b/entities/doodad.go index bef8722a..4e002123 100644 --- a/entities/doodad.go +++ b/entities/doodad.go @@ -57,3 +57,10 @@ func (d *Doodad) SetPos(x, y float64) { d.R.SetPos(x, y) } } + +// GetRenmderable retrieves the renderable. +// Mainly used to satisfy upper level interfaces. +// TODO: remove along with entity rework +func (d *Doodad) GetRenderable() render.Renderable { + return d.R +} diff --git a/entities/x/move/topdown.go b/entities/x/move/topdown.go index 8d16bbd5..bfb4a69e 100644 --- a/entities/x/move/topdown.go +++ b/entities/x/move/topdown.go @@ -9,12 +9,12 @@ import ( // WASD moves the given mover based on its speed as W,A,S, and D are pressed func WASD(mvr Mover) { - TopDown(mvr, key.W, key.S, key.A, key.D) + TopDown(mvr, key.WStr, key.SStr, key.AStr, key.DStr) } // Arrows moves the given mover based on its speed as the arrow keys are pressed func Arrows(mvr Mover) { - TopDown(mvr, key.UpArrow, key.DownArrow, key.LeftArrow, key.RightAlt) + TopDown(mvr, key.UpArrowStr, key.DownArrowStr, key.LeftArrowStr, key.RightAltStr) } // TopDown moves the given mover based on its speed as the given keys are pressed From 133cfba45d3348efe27244b64357cf31b1b241d9 Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 11:05:54 -0400 Subject: [PATCH 28/41] examples: Fixes for CID changes and key changes --- examples/click-propagation/main.go | 2 +- examples/clipboard/go.mod | 2 +- examples/clipboard/main.go | 16 ++--- examples/custom-cursor/main.go | 10 +-- examples/fallback-font/go.mod | 2 +- examples/flappy-bird/main.go | 54 ++++++++-------- examples/joystick-viz/main.go | 11 ++-- examples/multi-window/main.go | 8 +-- examples/particle-demo/main.go | 3 +- examples/piano/main.go | 61 +++++++++---------- .../platformer-tutorial/2-moving/moving.go | 14 ++--- .../platformer-tutorial/3-falling/falling.go | 22 +++---- .../platformer-tutorial/4-jumping/jumping.go | 25 ++++---- .../5-correct-jumping/correct-jumping.go | 11 ++-- .../6-complete/complete.go | 11 ++-- examples/pong/main.go | 4 +- examples/radar-demo/main.go | 19 +++--- examples/svg/go.mod | 2 +- 18 files changed, 132 insertions(+), 145 deletions(-) diff --git a/examples/click-propagation/main.go b/examples/click-propagation/main.go index 72f591c1..7ada4e5a 100644 --- a/examples/click-propagation/main.go +++ b/examples/click-propagation/main.go @@ -53,7 +53,7 @@ func newHoverButton(ctx *scene.Context, x, y, w, h float64, clr color.RGBA, laye sp.SetZLayer(float64(layer)) mouse.Add(sp) - mouse.PhaseCollision(sp, ctx.GetCallerMap(), ctx.Handler) + mouse.PhaseCollision(sp, ctx.Handler) render.Draw(hb.changingColorBox, 0, layer) diff --git a/examples/clipboard/go.mod b/examples/clipboard/go.mod index f55b9172..15d5fc95 100644 --- a/examples/clipboard/go.mod +++ b/examples/clipboard/go.mod @@ -1,6 +1,6 @@ module github.com/oakmound/oak/examples/clipboard -go 1.16 +go 1.18 require ( github.com/atotto/clipboard v0.1.4 diff --git a/examples/clipboard/main.go b/examples/clipboard/main.go index da8db2f3..af38afb5 100644 --- a/examples/clipboard/main.go +++ b/examples/clipboard/main.go @@ -3,16 +3,15 @@ package main import ( "fmt" - gokey "golang.org/x/mobile/event/key" - + "github.com/atotto/clipboard" "github.com/oakmound/oak/v3" "github.com/oakmound/oak/v3/entities/x/btn" "github.com/oakmound/oak/v3/event" "github.com/oakmound/oak/v3/key" + "github.com/oakmound/oak/v3/mouse" "github.com/oakmound/oak/v3/render" "github.com/oakmound/oak/v3/scene" - - "github.com/atotto/clipboard" + gokey "golang.org/x/mobile/event/key" ) func main() { @@ -33,7 +32,7 @@ func newClipboardCopyText(text string, x, y float64) { btn.Pos(x, y), btn.Height(20), btn.FitText(20), - btn.Click(func(event.CallerID, interface{}) int { + btn.Click(func(b btn.Btn, me *mouse.Event) event.Response { err := clipboard.WriteAll(text) if err != nil { fmt.Println(err) @@ -46,14 +45,15 @@ func newClipboardCopyText(text string, x, y float64) { func newClipboardPaster(placeholder string, x, y float64) { textPtr := new(string) *textPtr = placeholder + btn.New( btn.Font(render.DefaultFont()), btn.TextPtr(textPtr), btn.Pos(x, y), btn.Height(20), btn.FitText(20), - btn.Binding(key.Down+key.V, func(_ event.CallerID, payload interface{}) int { - kv := payload.(key.Event) + btn.Binding(key.Down(key.V), func(b btn.Btn, kv key.Event) event.Response { + if kv.Modifiers&gokey.ModControl == gokey.ModControl { got, err := clipboard.ReadAll() if err != nil { @@ -64,7 +64,7 @@ func newClipboardPaster(placeholder string, x, y float64) { } return 0 }), - btn.Click(func(event.CallerID, interface{}) int { + btn.Click(func(b btn.Btn, me *mouse.Event) event.Response { got, err := clipboard.ReadAll() if err != nil { fmt.Println(err) diff --git a/examples/custom-cursor/main.go b/examples/custom-cursor/main.go index 898fbed4..a7d391a9 100644 --- a/examples/custom-cursor/main.go +++ b/examples/custom-cursor/main.go @@ -37,11 +37,11 @@ func main() { ) ctx.DrawStack.Draw(box) - ctx.Handler.GlobalBind(mouse.Drag, func(_ event.CallerID, me interface{}) int { - mouseEvent := me.(*mouse.Event) - box.SetPos(mouseEvent.X(), mouseEvent.Y()) - return 0 - }) + event.GlobalBind(ctx, + mouse.Drag, func(mouseEvent *mouse.Event) event.Response { + box.SetPos(mouseEvent.X(), mouseEvent.Y()) + return 0 + }) }, }) oak.Init("customcursor") diff --git a/examples/fallback-font/go.mod b/examples/fallback-font/go.mod index 6ae55a2d..bc069ee2 100644 --- a/examples/fallback-font/go.mod +++ b/examples/fallback-font/go.mod @@ -1,6 +1,6 @@ module github.com/oakmound/oak/examples/fallback-font -go 1.16 +go 1.18 require ( github.com/flopp/go-findfont v0.0.0-20201114153133-e7393a00c15b diff --git a/examples/flappy-bird/main.go b/examples/flappy-bird/main.go index ec363dfe..f8516b85 100644 --- a/examples/flappy-bird/main.go +++ b/examples/flappy-bird/main.go @@ -37,11 +37,11 @@ func main() { score = 0 // 1. Make Player - newFlappy(90, 140) + newFlappy(ctx, 90, 140) // 2. Make scrolling repeating pillars var pillarLoop func() pillarLoop = func() { - newPillarPair() + newPillarPair(ctx) ctx.DoAfter(time.Duration(pillarFreq.Poll()*float64(time.Second)), pillarLoop) } go ctx.DoAfter(time.Duration(pillarFreq.Poll()*float64(time.Second)), pillarLoop) @@ -49,12 +49,6 @@ func main() { // 3. Make Score t := render.DefaultFont().NewIntText(&score, 200, 30) render.Draw(t, 0) - }, Loop: func() bool { - if playerHitPillar { - playerHitPillar = false - return false - } - return true }, End: func() (string, *scene.Result) { return "bounce", nil }}) @@ -69,25 +63,24 @@ type Flappy struct { *entities.Interactive } -// Init satisfies the event.Entity interface -func (f *Flappy) Init() event.CallerID { - return event.NextID(f) +// CID returns the event.CallerID so that this can be bound to. +func (flap *Flappy) CID() event.CallerID { + return flap.CallerID } -func newFlappy(x, y float64) *Flappy { +func newFlappy(ctx *scene.Context, x, y float64) *Flappy { f := new(Flappy) - f.Interactive = entities.NewInteractive(x, y, 32, 32, render.NewColorBox(32, 32, color.RGBA{0, 255, 255, 255}), nil, f.Init(), 1) + f.Interactive = entities.NewInteractive(x, y, 32, 32, render.NewColorBox(32, 32, color.RGBA{0, 255, 255, 255}), nil, ctx.Register(f), 1) f.RSpace.Add(pillar, func(s1, s2 *collision.Space) { - playerHitPillar = true + ctx.Window.NextScene() }) f.RSpace.Space.Label = player collision.Add(f.RSpace.Space) f.R.SetLayer(1) render.Draw(f.R, 0) - - f.Bind(event.Enter, func(event.CallerID, interface{}) int { + event.Bind(ctx, event.Enter, f, func(f *Flappy, ev event.EnterPayload) event.Response { f.ShiftPos(f.Delta.X(), f.Delta.Y()) f.Add(f.Delta) if f.Delta.Y() > 10 { @@ -101,7 +94,7 @@ func newFlappy(x, y float64) *Flappy { <-f.RSpace.CallOnHits() if f.Y()+f.H > 480 { - playerHitPillar = true + ctx.Window.NextScene() } if f.Y() < 0 { f.SetY(0) @@ -109,11 +102,12 @@ func newFlappy(x, y float64) *Flappy { } return 0 }) - f.Bind(mouse.Press, func(event.CallerID, interface{}) int { + + event.Bind(ctx, mouse.Press, f, func(f *Flappy, me *mouse.Event) event.Response { f.Delta.ShiftY(-4) return 0 }) - f.Bind(key.Down+key.W, func(event.CallerID, interface{}) int { + event.Bind(ctx, key.Down(key.W), f, func(f *Flappy, k key.Event) event.Response { f.Delta.ShiftY(-4) return 0 }) @@ -126,17 +120,18 @@ type Pillar struct { hasScored bool } -// Init satisfies the event.Entity interface -func (p *Pillar) Init() event.CallerID { - return event.NextID(p) +// CID returns the event.CallerID so that this can be bound to. +func (p *Pillar) CID() event.CallerID { + return p.CallerID } -func newPillar(x, y, h float64, isAbove bool) { +func newPillar(ctx *scene.Context, x, y, h float64, isAbove bool) { p := new(Pillar) - p.Solid = entities.NewSolid(x, y, 64, h, render.NewColorBox(64, int(h), color.RGBA{0, 255, 0, 255}), nil, p.Init()) + p.Solid = entities.NewSolid(x, y, 64, h, render.NewColorBox(64, int(h), color.RGBA{0, 255, 0, 255}), nil, ctx.Register(p)) p.Space.Label = pillar collision.Add(p.Space) - p.Bind(event.Enter, enterPillar) + event.Bind(ctx, event.Enter, p, enterPillar) + p.R.SetLayer(1) render.Draw(p.R, 0) // Don't score one out of each two pillars @@ -145,7 +140,7 @@ func newPillar(x, y, h float64, isAbove bool) { } } -func newPillarPair() { +func newPillarPair(ctx *scene.Context) { pos := gapPosition.Poll() span := gapSpan.Poll() if (pos + span) > 470 { @@ -155,12 +150,11 @@ func newPillarPair() { pos = 370 span = 100 } - newPillar(641, 0, pos, true) - newPillar(641, pos+span, 480-(pos+span), false) + newPillar(ctx, 641, 0, pos, true) + newPillar(ctx, 641, pos+span, 480-(pos+span), false) } -func enterPillar(id event.CallerID, nothing interface{}) int { - p := event.GetEntity(id).(*Pillar) +func enterPillar(p *Pillar, ev event.EnterPayload) event.Response { p.ShiftX(-2) if p.X()+p.W < 0 { p.Destroy() diff --git a/examples/joystick-viz/main.go b/examples/joystick-viz/main.go index 6980aa92..6061b7e0 100644 --- a/examples/joystick-viz/main.go +++ b/examples/joystick-viz/main.go @@ -22,13 +22,16 @@ func main() { *latestInput = "Latest Input: Keyboard+Mouse" ctx.DrawStack.Draw(render.NewStrPtrText(latestInput, 10, 460), 4) ctx.DrawStack.Draw(render.NewText("Space to Vibrate", 10, 440), 4) - ctx.Handler.GlobalBind(event.InputChange, func(_ event.CallerID, payload interface{}) int { - input := payload.(oak.InputType) + + event.GlobalBind(ctx, oak.InputChange, func(input oak.InputType) event.Response { + switch input { case oak.InputJoystick: *latestInput = "Latest Input: Joystick" - case oak.InputKeyboardMouse: - *latestInput = "Latest Input: Keyboard+Mouse" + case oak.InputKeyboard: + *latestInput = "Latest Input: Keyboard" + case oak.InputMouse: + *latestInput = "Latest Input: Mouse" } return 0 }) diff --git a/examples/multi-window/main.go b/examples/multi-window/main.go index 4407c8cd..120e0840 100644 --- a/examples/multi-window/main.go +++ b/examples/multi-window/main.go @@ -26,10 +26,10 @@ func main() { ctx.DrawStack.Draw(cb, 0) dFPS := render.NewDrawFPS(0.1, nil, 600, 10) ctx.DrawStack.Draw(dFPS, 1) - ctx.Handler.GlobalBind(mouse.Press, mouse.Binding(func(_ event.CallerID, me *mouse.Event) int { + event.GlobalBind(ctx, mouse.Press, func(me *mouse.Event) event.Response { cb.SetPos(me.X(), me.Y()) return 0 - })) + }) }, }) go func() { @@ -55,10 +55,10 @@ func main() { ctx.DrawStack.Draw(cb, 0) dFPS := render.NewDrawFPS(0.1, nil, 600, 10) ctx.DrawStack.Draw(dFPS, 1) - ctx.Handler.GlobalBind(mouse.Press, mouse.Binding(func(_ event.CallerID, me *mouse.Event) int { + event.GlobalBind(ctx, mouse.Press, func(me *mouse.Event) event.Response { cb.SetPos(me.X(), me.Y()) return 0 - })) + }) }, }) c2.Init("scene2", func(c oak.Config) (oak.Config, error) { diff --git a/examples/particle-demo/main.go b/examples/particle-demo/main.go index ab91c43c..238f9d87 100644 --- a/examples/particle-demo/main.go +++ b/examples/particle-demo/main.go @@ -56,7 +56,7 @@ func parseShape(args []string) shape.Shape { func main() { debugstream.AddCommand(debugstream.Command{Name: "followMouse", Operation: func(args []string) string { - event.GlobalBind(event.Enter, func(event.CallerID, interface{}) int { + event.GlobalBind(event.DefaultBus, event.Enter, func(ev event.EnterPayload) event.Response { // It'd be interesting to attach to the mouse position src.SetPos(float64(mouse.LastEvent.X()), float64(mouse.LastEvent.Y())) return 0 @@ -284,6 +284,7 @@ func main() { endColor = color.RGBA{255, 255, 255, 255} endColorRand = color.RGBA{0, 0, 0, 0} shape := shape.Square + src = pt.NewColorGenerator( pt.Pos(x, y), pt.Duration(pt.Inf), diff --git a/examples/piano/main.go b/examples/piano/main.go index b9bd2c1f..a5ab0c02 100644 --- a/examples/piano/main.go +++ b/examples/piano/main.go @@ -60,7 +60,7 @@ func (kc keyColor) Color() color.RGBA { return color.RGBA{255, 255, 255, 255} } -func newKey(note synth.Pitch, c keyColor, k string) *entities.Solid { +func newKey(ctx *scene.Context, note synth.Pitch, c keyColor, k string) *entities.Solid { w := c.Width() h := c.Height() clr := c.Color() @@ -93,30 +93,29 @@ func newKey(note synth.Pitch, c keyColor, k string) *entities.Solid { s.Space.Label = labelWhiteKey } mouse.UpdateSpace(s.X(), s.Y(), s.W, s.H, s.Space) - s.Bind(key.Down+k, func(c event.CallerID, i interface{}) int { - if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { + event.GlobalBind(ctx, key.Down(key.AllKeys[k]), func(_ key.Event) event.Response { + if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { return 0 } - playPitch(note) + playPitch(ctx, note) sw.Set("down") return 0 }) - s.Bind(key.Up+k, func(c event.CallerID, i interface{}) int { - if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { + event.GlobalBind(ctx, key.Up(key.AllKeys[k]), func(_ key.Event) event.Response { + if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { return 0 } releasePitch(note) sw.Set("up") return 0 }) - s.Bind(mouse.PressOn, func(c event.CallerID, i interface{}) int { - playPitch(note) - me := i.(*mouse.Event) + event.Bind(ctx, mouse.PressOn, s, func(_ *entities.Solid, me *mouse.Event) event.Response { + playPitch(ctx, note) me.StopPropagation = true sw.Set("down") return 0 }) - s.Bind(mouse.Release, func(c event.CallerID, i interface{}) int { + event.Bind(ctx, mouse.Release, s, func(_ *entities.Solid, me *mouse.Event) event.Response { releasePitch(note) sw.Set("up") return 0 @@ -133,10 +132,10 @@ type keyDef struct { var keycharOrder = []string{ "Z", "S", "X", "D", "C", "V", "G", "B", "H", "N", "J", "M", - key.Comma, "L", key.Period, key.Semicolon, key.Slash, + key.CommaStr, "L", key.Period, key.SemicolonStr, key.SlashStr, "Q", "2", "W", "3", "E", "4", "R", "T", "6", "Y", "7", "U", - "I", "9", "O", "0", "P", key.HyphenMinus, key.LeftSquareBracket, + "I", "9", "O", "0", "P", key.HyphenMinusStr, key.LeftSquareBracketStr, } var playLock sync.Mutex @@ -144,7 +143,7 @@ var cancelFuncs = map[synth.Pitch]func(){} var synthKind func(...synth.Option) (pcm.Reader, error) -func playPitch(pitch synth.Pitch) { +func playPitch(ctx *scene.Context, pitch synth.Pitch) { playLock.Lock() defer playLock.Unlock() if cancel, ok := cancelFuncs[pitch]; ok { @@ -158,12 +157,12 @@ func playPitch(pitch synth.Pitch) { fmt.Println("new writer failed:", err) return } - monitor := newPCMMonitor(speaker) + monitor := newPCMMonitor(ctx, speaker) monitor.SetPos(0, 0) render.Draw(monitor) - ctx, cancel := context.WithCancel(context.Background()) + gctx, cancel := context.WithCancel(ctx) go func() { - err = pcm.Play(ctx, monitor, toPlay) + err = pcm.Play(gctx, monitor, toPlay) if err != nil { fmt.Println("play error:", err) } @@ -204,7 +203,7 @@ func main() { y := 200.0 i := 0 for i < len(keycharOrder) && x+kc.Width() < float64(ctx.Window.Width()-10) { - ky := newKey(pitch, kc, keycharOrder[i]) + ky := newKey(ctx, pitch, kc, keycharOrder[i]) ky.SetPos(x, y) layer := 0 if kc == keyColorBlack { @@ -223,26 +222,26 @@ func main() { i++ } // Consider: Adding volume control - event.GlobalBind(key.Down+key.S, func(c event.CallerID, i interface{}) int { - if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { + event.GlobalBind(ctx, key.Down(key.S), func(_ key.Event) event.Response { + if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { synthKind = src.SinPCM } return 0 }) - event.GlobalBind(key.Down+key.W, func(c event.CallerID, i interface{}) int { - if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { + event.GlobalBind(ctx, key.Down(key.W), func(_ key.Event) event.Response { + if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { synthKind = src.SawPCM } return 0 }) - event.GlobalBind(key.Down+key.T, func(c event.CallerID, i interface{}) int { - if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { + event.GlobalBind(ctx, key.Down(key.T), func(_ key.Event) event.Response { + if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { synthKind = src.TrianglePCM } return 0 }) - event.GlobalBind(key.Down+key.P, func(c event.CallerID, i interface{}) int { - if oak.IsDown(key.LeftShift) || oak.IsDown(key.RightShift) { + event.GlobalBind(ctx, key.Down(key.P), func(_ key.Event) event.Response { + if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { synthKind = src.PulsePCM(2) } return 0 @@ -252,7 +251,7 @@ func main() { render.Draw(help1) render.Draw(help2) - event.GlobalBind(mouse.ScrollDown, func(c event.CallerID, i interface{}) int { + event.GlobalBind(ctx, mouse.ScrollDown, func(_ *mouse.Event) event.Response { mag := globalMagnification - 0.05 if mag < 1 { mag = 1 @@ -260,7 +259,7 @@ func main() { globalMagnification = mag return 0 }) - event.GlobalBind(mouse.ScrollUp, func(c event.CallerID, i interface{}) int { + event.GlobalBind(ctx, mouse.ScrollUp, func(_ *mouse.Event) event.Response { globalMagnification += 0.05 return 0 }) @@ -285,7 +284,7 @@ type pcmMonitor struct { var globalMagnification float64 = 1 -func newPCMMonitor(w pcm.Writer) *pcmMonitor { +func newPCMMonitor(ctx *scene.Context, w pcm.Writer) *pcmMonitor { fmt := w.PCMFormat() pm := &pcmMonitor{ Writer: w, @@ -293,13 +292,11 @@ func newPCMMonitor(w pcm.Writer) *pcmMonitor { LayeredPoint: render.NewLayeredPoint(0, 0, 0), written: make([]byte, fmt.BytesPerSecond()*pcm.WriterBufferLengthInSeconds), } - pm.Init() return pm } -func (pm *pcmMonitor) Init() event.CallerID { - pm.CID = event.NextID(pm) - return pm.CID +func (pm *pcmMonitor) CID() event.CallerID { + return pm.CallerID } func (pm *pcmMonitor) PCMFormat() pcm.Format { diff --git a/examples/platformer-tutorial/2-moving/moving.go b/examples/platformer-tutorial/2-moving/moving.go index 0ab7b1e2..07d27096 100644 --- a/examples/platformer-tutorial/2-moving/moving.go +++ b/examples/platformer-tutorial/2-moving/moving.go @@ -15,7 +15,7 @@ import ( ) func main() { - oak.AddScene("platformer", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("platformer", scene.Scene{Start: func(ctx *scene.Context) { char := entities.NewMoving(100, 100, 16, 32, render.NewColorBox(16, 32, color.RGBA{255, 0, 0, 255}), @@ -24,15 +24,13 @@ func main() { render.Draw(char.R) char.Speed = physics.NewVector(3, 3) - - char.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { - char := event.GetEntity(id).(*entities.Moving) + event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { // Move left and right with A and D - if oak.IsDown(key.A) { - char.ShiftX(-char.Speed.X()) + if oak.IsDown(key.AStr) { + c.ShiftX(-c.Speed.X()) } - if oak.IsDown(key.D) { - char.ShiftX(char.Speed.X()) + if oak.IsDown(key.DStr) { + c.ShiftX(c.Speed.X()) } return 0 }) diff --git a/examples/platformer-tutorial/3-falling/falling.go b/examples/platformer-tutorial/3-falling/falling.go index 9f3c9b66..beabdd44 100644 --- a/examples/platformer-tutorial/3-falling/falling.go +++ b/examples/platformer-tutorial/3-falling/falling.go @@ -24,7 +24,7 @@ const ( ) func main() { - oak.AddScene("platformer", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("platformer", scene.Scene{Start: func(ctx *scene.Context) { char := entities.NewMoving(100, 100, 16, 32, render.NewColorBox(16, 32, color.RGBA{255, 0, 0, 255}), @@ -36,23 +36,23 @@ func main() { fallSpeed := .1 - char.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { - char := event.GetEntity(id).(*entities.Moving) + event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { + // Move left and right with A and D - if oak.IsDown(key.A) { - char.ShiftX(-char.Speed.X()) + if oak.IsDown(key.AStr) { + c.ShiftX(-c.Speed.X()) } - if oak.IsDown(key.D) { - char.ShiftX(char.Speed.X()) + if oak.IsDown(key.DStr) { + c.ShiftX(c.Speed.X()) } - hit := char.HitLabel(Ground) + hit := c.HitLabel(Ground) if hit == nil { // Fall if there's no ground - char.Delta.ShiftY(fallSpeed) + c.Delta.ShiftY(fallSpeed) } else { - char.Delta.SetY(0) + c.Delta.SetY(0) } - char.ShiftY(char.Delta.Y()) + c.ShiftY(c.Delta.Y()) return 0 }) diff --git a/examples/platformer-tutorial/4-jumping/jumping.go b/examples/platformer-tutorial/4-jumping/jumping.go index 1f89019b..41c950a8 100644 --- a/examples/platformer-tutorial/4-jumping/jumping.go +++ b/examples/platformer-tutorial/4-jumping/jumping.go @@ -24,7 +24,7 @@ const ( ) func main() { - oak.AddScene("platformer", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("platformer", scene.Scene{Start: func(ctx *scene.Context) { char := entities.NewMoving(100, 100, 16, 32, render.NewColorBox(16, 32, color.RGBA{255, 0, 0, 255}), @@ -36,27 +36,26 @@ func main() { fallSpeed := .1 - char.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { - char := event.GetEntity(id).(*entities.Moving) + event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { // Move left and right with A and D - if oak.IsDown(key.A) { - char.ShiftX(-char.Speed.X()) + if oak.IsDown(key.AStr) { + c.ShiftX(-c.Speed.X()) } - if oak.IsDown(key.D) { - char.ShiftX(char.Speed.X()) + if oak.IsDown(key.DStr) { + c.ShiftX(c.Speed.X()) } - hit := collision.HitLabel(char.Space, Ground) + hit := collision.HitLabel(c.Space, Ground) if hit == nil { // Fall if there's no ground - char.Delta.ShiftY(fallSpeed) + c.Delta.ShiftY(fallSpeed) } else { - char.Delta.SetY(0) + c.Delta.SetY(0) // Jump with Space - if oak.IsDown(key.Spacebar) { - char.Delta.ShiftY(-char.Speed.Y()) + if oak.IsDown(key.SpacebarStr) { + c.Delta.ShiftY(-c.Speed.Y()) } } - char.ShiftY(char.Delta.Y()) + c.ShiftY(c.Delta.Y()) return 0 }) diff --git a/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go b/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go index 60fc9ce1..4d2c381e 100644 --- a/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go +++ b/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go @@ -24,7 +24,7 @@ const ( ) func main() { - oak.AddScene("platformer", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("platformer", scene.Scene{Start: func(ctx *scene.Context) { char := entities.NewMoving(100, 100, 16, 32, render.NewColorBox(16, 32, color.RGBA{255, 0, 0, 255}), @@ -36,13 +36,12 @@ func main() { fallSpeed := .1 - char.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { - char := event.GetEntity(id).(*entities.Moving) + event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { // Move left and right with A and D - if oak.IsDown(key.A) { + if oak.IsDown(key.AStr) { char.ShiftX(-char.Speed.X()) } - if oak.IsDown(key.D) { + if oak.IsDown(key.DStr) { char.ShiftX(char.Speed.X()) } oldY := char.Y() @@ -56,7 +55,7 @@ func main() { char.SetY(hit.Y() - char.H) char.Delta.SetY(0) // Jump with Space - if oak.IsDown(key.Spacebar) { + if oak.IsDown(key.SpacebarStr) { char.Delta.ShiftY(-char.Speed.Y()) } } else { diff --git a/examples/platformer-tutorial/6-complete/complete.go b/examples/platformer-tutorial/6-complete/complete.go index 7b481368..12a5b5d7 100644 --- a/examples/platformer-tutorial/6-complete/complete.go +++ b/examples/platformer-tutorial/6-complete/complete.go @@ -26,7 +26,7 @@ const ( ) func main() { - oak.AddScene("platformer", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("platformer", scene.Scene{Start: func(ctx *scene.Context) { char := entities.NewMoving(100, 100, 16, 32, render.NewColorBox(16, 32, color.RGBA{255, 0, 0, 255}), @@ -38,13 +38,12 @@ func main() { fallSpeed := .2 - char.Bind(event.Enter, func(id event.CallerID, nothing interface{}) int { - char := event.GetEntity(id).(*entities.Moving) + event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { // Move left and right with A and D - if oak.IsDown(key.A) { + if oak.IsDown(key.AStr) { char.Delta.SetX(-char.Speed.X()) - } else if oak.IsDown(key.D) { + } else if oak.IsDown(key.DStr) { char.Delta.SetX(char.Speed.X()) } else { char.Delta.SetX(0) @@ -64,7 +63,7 @@ func main() { // Stop falling char.Delta.SetY(0) // Jump with Space when on the ground - if oak.IsDown(key.Spacebar) { + if oak.IsDown(key.SpacebarStr) { char.Delta.ShiftY(-char.Speed.Y()) } aboveGround = true diff --git a/examples/pong/main.go b/examples/pong/main.go index 3dba7009..bcec120f 100644 --- a/examples/pong/main.go +++ b/examples/pong/main.go @@ -76,9 +76,9 @@ func newPaddle(ctx *scene.Context, x, y float64, player int) { render.Draw(p.R, 1) p.Space.UpdateLabel(hitPaddle) if player == 1 { - event.Bind(ctx, event.Enter, p, enterPaddle(key.UpArrow, key.DownArrow)) + event.Bind(ctx, event.Enter, p, enterPaddle(key.UpArrowStr, key.DownArrowStr)) } else { - event.Bind(ctx, event.Enter, p, enterPaddle(key.W, key.S)) + event.Bind(ctx, event.Enter, p, enterPaddle(key.WStr, key.SStr)) } } diff --git a/examples/radar-demo/main.go b/examples/radar-demo/main.go index 201a961f..981ceea5 100644 --- a/examples/radar-demo/main.go +++ b/examples/radar-demo/main.go @@ -26,6 +26,8 @@ const ( // This example demonstrates making a basic radar or other custom renderable // type. The radar here acts as a UI element, staying on screen, and follows // around a player character. +//TODO: Remove and or link to grove radar as it is cleaner +// https://github.com/oakmound/grove/tree/master/components/radar func main() { oak.AddScene("demo", scene.Scene{Start: func(ctx *scene.Context) { @@ -36,8 +38,7 @@ func main() { oak.SetViewportBounds(intgeom.NewRect2(0, 0, xLimit, yLimit)) moveRect := floatgeom.NewRect2(0, 0, xLimit, yLimit) - - char.Bind(event.Enter, func(event.CallerID, interface{}) int { + event.Bind(ctx, event.Enter, char, func(char *entities.Moving, ev event.EnterPayload) event.Response { move.WASD(char) move.Limit(char, moveRect) move.CenterScreenOn(char) @@ -55,8 +56,8 @@ func main() { for i := 0; i < 5; i++ { x, y := rand.Float64()*400, rand.Float64()*400 - enemy := newEnemyOnRadar(x, y) - enemy.CID.Bind(event.Enter, standardEnemyMove) + enemy := newEnemyOnRadar(ctx, x, y) + event.Bind(ctx, event.Enter, enemy, standardEnemyMove) render.Draw(enemy.R, 1, 1) r.AddPoint(radar.Point{X: enemy.Xp(), Y: enemy.Yp()}, color.RGBA{255, 255, 0, 255}) } @@ -87,19 +88,15 @@ type enemyOnRadar struct { *entities.Moving } -func (eor *enemyOnRadar) Init() event.CallerID { - return event.NextID(eor) -} -func newEnemyOnRadar(x, y float64) *enemyOnRadar { +func newEnemyOnRadar(ctx *scene.Context, x, y float64) *enemyOnRadar { eor := new(enemyOnRadar) - eor.Moving = entities.NewMoving(50, y, 50, 50, render.NewColorBox(25, 25, color.RGBA{0, 200, 0, 0}), nil, eor.Init(), 0) + eor.Moving = entities.NewMoving(50, y, 50, 50, render.NewColorBox(25, 25, color.RGBA{0, 200, 0, 0}), nil, ctx.Register(eor), 0) eor.Speed = physics.NewVector(-1*(rand.Float64()*2+1), rand.Float64()*2-1) eor.Delta = eor.Speed return eor } -func standardEnemyMove(id event.CallerID, nothing interface{}) int { - eor := event.GetEntity(id).(*enemyOnRadar) +func standardEnemyMove(eor *enemyOnRadar, ev event.EnterPayload) event.Response { if eor.X() < 0 { eor.Delta.SetPos(math.Abs(eor.Speed.X()), (eor.Speed.Y())) } diff --git a/examples/svg/go.mod b/examples/svg/go.mod index c85ce234..813b2a57 100644 --- a/examples/svg/go.mod +++ b/examples/svg/go.mod @@ -1,6 +1,6 @@ module github.com/oakmound/oak/examples/svg -go 1.16 +go 1.18 require ( github.com/oakmound/oak/v3 v3.0.0-alpha.1 From 710c7705ed5eec2b0ffd44c26b06e2d5c1672634 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 2 Apr 2022 10:29:33 -0500 Subject: [PATCH 29/41] oak: drop background smear todo, correct key usage docs --- key/keys.go | 4 ++-- lifecycle.go | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/key/keys.go b/key/keys.go index 1cb73fc9..78c58a63 100644 --- a/key/keys.go +++ b/key/keys.go @@ -4,8 +4,8 @@ package key // This list is not used internally by oak, but was generated from // the expected output from x/mobile/key. // -// These strings are sent as payloads to Key.Down and Key.Up events, -// and through "KeyDown"+$a, "KeyUp"+$a for any $a in the const. +// These strings are sent as payloads to AnyDown and AnyUp events, +// and through Down(k) and Up(k). const ( Unknown = "Unknown" diff --git a/lifecycle.go b/lifecycle.go index 795a0860..49511503 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -77,9 +77,7 @@ func (w *Window) SetAspectRatio(xToY float64) { // ChangeWindow sets the width and height of the game window. Although exported, // calling it without a size event will probably not act as expected. func (w *Window) ChangeWindow(width, height int) error { - // Draw a black frame to cover up smears - // Todo: could restrict the black to -just- the area not covered by the - // scaled screen buffer + // Draw the background to cover up smears buff, err := w.screenControl.NewImage(image.Point{width, height}) if err == nil { draw.Draw(buff.RGBA(), buff.Bounds(), w.bkgFn(), zeroPoint, draw.Src) From 3ea313cbfc724f07621391648633a70e6676ce30 Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 11:56:27 -0400 Subject: [PATCH 30/41] entities: Fixes new solids that dont have a cid passed in sharing collision --- entities/solid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entities/solid.go b/entities/solid.go index 934ce90b..357f374c 100644 --- a/entities/solid.go +++ b/entities/solid.go @@ -31,7 +31,7 @@ func NewSolid(x, y, w, h float64, r render.Renderable, tree *collision.Tree, cid tree = collision.DefaultTree } s.Tree = tree - s.Space = collision.NewSpace(x, y, w, h, cid) + s.Space = collision.NewSpace(x, y, w, h, s.CallerID) s.Tree.Add(s.Space) return s } From ac0771a957a5e74ad98e22553db6b35af699d9e2 Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 12:02:35 -0400 Subject: [PATCH 31/41] examples: Update more examples to the new event paradigm --- examples/rooms/main.go | 2 +- examples/screenopts/main.go | 7 +- examples/sprite-demo/main.go | 26 +++---- examples/svg/go.mod | 20 +++++ examples/svg/go.sum | 4 - examples/text-demo-1/main.go | 30 ++++---- examples/text-demo-2/main.go | 40 +++++----- examples/titlescreen-demo/main.go | 77 ++++++++++--------- .../1-start/start.go | 14 ++-- .../2-shooting/shooting.go | 28 +++---- .../3-enemies/enemies.go | 44 ++++------- .../4-sprites/sprites.go | 37 ++++----- examples/zooming/main.go | 12 +-- 13 files changed, 165 insertions(+), 176 deletions(-) diff --git a/examples/rooms/main.go b/examples/rooms/main.go index 91e02fa5..b9d3ad74 100644 --- a/examples/rooms/main.go +++ b/examples/rooms/main.go @@ -49,7 +49,7 @@ func main() { var transitioning bool var totalTransitionDelta intgeom.Point2 var transitionDelta intgeom.Point2 - char.Bind(event.Enter, func(event.CallerID, interface{}) int { + event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { dir, ok := isOffScreen(ctx, char) if !transitioning && ok { transitioning = true diff --git a/examples/screenopts/main.go b/examples/screenopts/main.go index a0398b03..d0dd3bde 100644 --- a/examples/screenopts/main.go +++ b/examples/screenopts/main.go @@ -16,14 +16,13 @@ const ( ) func main() { - oak.AddScene("demo", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("demo", scene.Scene{Start: func(ctx *scene.Context) { txt := render.NewText("Press F to toggle fullscreen. Press B to toggle borderless.", 50, 50) render.Draw(txt) borderless := borderlessAtStart fullscreen := fullscreenAtStart - - event.GlobalBind(key.Down+key.F, func(event.CallerID, interface{}) int { + event.GlobalBind(ctx, key.Down(key.W), func(k key.Event) event.Response { fullscreen = !fullscreen err := oak.SetFullScreen(fullscreen) if err != nil { @@ -32,7 +31,7 @@ func main() { } return 0 }) - event.GlobalBind(key.Down+key.B, func(event.CallerID, interface{}) int { + event.GlobalBind(ctx, key.Down(key.B), func(k key.Event) event.Response { borderless = !borderless err := oak.SetBorderless(borderless) if err != nil { diff --git a/examples/sprite-demo/main.go b/examples/sprite-demo/main.go index 20d7eb89..ac82350c 100644 --- a/examples/sprite-demo/main.go +++ b/examples/sprite-demo/main.go @@ -27,7 +27,7 @@ var cache = [360]*image.RGBA{} func main() { oak.AddScene( "demo", - scene.Scene{Start: func(*scene.Context) { + scene.Scene{Start: func(ctx *scene.Context) { render.Draw(render.NewDrawFPS(0.03, nil, 10, 10)) render.Draw(render.NewLogicFPS(0.03, nil, 10, 20)) @@ -35,11 +35,11 @@ func main() { layerTxt := render.DefaultFont().NewIntText(&layer, 30, 20) layerTxt.SetLayer(100000000) render.Draw(layerTxt, 0) - NewGopher(layer) + NewGopher(ctx, layer) layer++ - event.GlobalBind(event.Enter, func(event.CallerID, interface{}) int { + event.GlobalBind(ctx, event.Enter, func(ev event.EnterPayload) event.Response { if oak.IsDown("K") { - NewGopher(layer) + NewGopher(ctx, layer) layer++ } return 0 @@ -76,31 +76,23 @@ type Gopher struct { rotation int } -// Init sets up a gophers CID -func (g *Gopher) Init() event.CallerID { - return event.NextID(g) -} - // NewGopher creates a gopher sprite to bounce around -func NewGopher(layer int) { - goph := Gopher{} +func NewGopher(ctx *scene.Context, layer int) { + goph := new(Gopher) goph.Doodad = entities.NewDoodad( rand.Float64()*576, rand.Float64()*416, render.NewSwitch("goph", map[string]render.Modifiable{"goph": render.EmptyRenderable()}), - //render.NewReverting(render.LoadSprite(filepath.Join("raw", "gopher11.png"))), - goph.Init()) + ctx.Register(goph)) goph.R.SetLayer(layer) - goph.Bind("EnterFrame", gophEnter) + event.Bind(ctx, event.Enter, goph, gophEnter) goph.deltaX = 4 * float64(rand.Intn(2)*2-1) goph.deltaY = 4 * float64(rand.Intn(2)*2-1) goph.rotation = rand.Intn(360) render.Draw(goph.R, 0) } -func gophEnter(cid event.CallerID, nothing interface{}) int { - goph := event.GetEntity(cid).(*Gopher) - +func gophEnter(goph *Gopher, ev event.EnterPayload) event.Response { // Compare against this version of rotation // (also swap the comments on lines in goph.Doodad's renderable) //goph.R.(*render.Reverting).RevertAndModify(1, render.Rotate(goph.rotation)) diff --git a/examples/svg/go.mod b/examples/svg/go.mod index 813b2a57..0f091b98 100644 --- a/examples/svg/go.mod +++ b/examples/svg/go.mod @@ -6,7 +6,27 @@ require ( github.com/oakmound/oak/v3 v3.0.0-alpha.1 github.com/srwiley/oksvg v0.0.0-20210320200257-875f767ac39a github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 +) + +require ( + dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037 // indirect + github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect + github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect + github.com/disintegration/gift v1.2.0 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/hajimehoshi/go-mp3 v0.3.1 // indirect + github.com/oakmound/alsa v0.0.2 // indirect + github.com/oakmound/libudev v0.2.1 // indirect + github.com/oakmound/w32 v2.1.0+incompatible // indirect + github.com/oov/directsound-go v0.0.0-20141101201356-e53e59c700bf // indirect + golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect golang.org/x/image v0.0.0-20210504121937-7319ad40d33e // indirect + golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 // indirect + golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect + golang.org/x/text v0.3.6 // indirect ) replace github.com/oakmound/oak/v3 => ../.. diff --git a/examples/svg/go.sum b/examples/svg/go.sum index e94799e5..8ec8315c 100644 --- a/examples/svg/go.sum +++ b/examples/svg/go.sum @@ -7,8 +7,6 @@ github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= github.com/disintegration/gift v1.2.0 h1:VMQeei2F+ZtsHjMgP6Sdt1kFjRhs2lGz8ljEOPeIR50= github.com/disintegration/gift v1.2.0/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= -github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d/go.mod h1:CHkHWWZ4kbGY6jEy1+qlitDaCtRgNvCOQdakj/1Yl/Q= -github.com/eaburns/flac v0.0.0-20171003200620-9a6fb92396d1/go.mod h1:frG94byMNy+1CgGrQ25dZ+17tf98EN+OYBQL4Zh612M= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= @@ -17,7 +15,6 @@ github.com/hajimehoshi/go-mp3 v0.3.1 h1:pn/SKU1+/rfK8KaZXdGEC2G/KCB2aLRjbTCrwKco github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/jfreymuth/pulse v0.1.0 h1:KN38/9hoF9PJvP5DpEVhMRKNuwnJUonc8c9ARorRXUA= -github.com/jfreymuth/pulse v0.1.0/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no= github.com/oakmound/alsa v0.0.2 h1:JbOUckkJqVvhABth7qy2JgAjqsWuBPggyoYOk1L6eK0= github.com/oakmound/alsa v0.0.2/go.mod h1:wx+ehwqFnNL7foTwxxu2bKQlaUmD2oXd4ka1UBSgWAo= github.com/oakmound/libudev v0.2.1 h1:gaXuw7Pbt3RSRxbUakAjl0dSW6Wo3TZWpwS5aMq8+EA= @@ -39,7 +36,6 @@ golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMD golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= diff --git a/examples/text-demo-1/main.go b/examples/text-demo-1/main.go index 0b6c7a30..5aa60be5 100644 --- a/examples/text-demo-1/main.go +++ b/examples/text-demo-1/main.go @@ -64,21 +64,23 @@ func main() { render.Draw(font2.NewText("r", 160, 260), 0) render.Draw(font2.NewText("g", 280, 260), 0) render.Draw(font2.NewText("b", 400, 260), 0) + + go func() { + for { + r = limit.EnforceRange(r + diff.Poll()) + g = limit.EnforceRange(g + diff.Poll()) + b = limit.EnforceRange(b + diff.Poll()) + font.Drawer.Src = image.NewUniform( + color.RGBA{ + uint8(r), + uint8(g), + uint8(b), + 255, + }, + ) + } + }() }, - Loop: func() bool { - r = limit.EnforceRange(r + diff.Poll()) - g = limit.EnforceRange(g + diff.Poll()) - b = limit.EnforceRange(b + diff.Poll()) - font.Drawer.Src = image.NewUniform( - color.RGBA{ - uint8(r), - uint8(g), - uint8(b), - 255, - }, - ) - return true - }, }) oak.SetFS(assets) oak.Init("demo") diff --git a/examples/text-demo-2/main.go b/examples/text-demo-2/main.go index 235f5c10..afe8cf38 100644 --- a/examples/text-demo-2/main.go +++ b/examples/text-demo-2/main.go @@ -60,26 +60,28 @@ func main() { strs = append(strs, font.NewText(str, 0, y)) render.Draw(strs[len(strs)-1], 0) } - }, - Loop: func() bool { - r = limit.EnforceRange(r + diff.Poll()) - g = limit.EnforceRange(g + diff.Poll()) - b = limit.EnforceRange(b + diff.Poll()) - // This should be a function in oak to just set color source - // (or texture source) - font.Drawer.Src = image.NewUniform( - color.RGBA{ - uint8(r), - uint8(g), - uint8(b), - 255, - }, - ) - for _, st := range strs { - st.SetString(randomStr(strlen)) + + go func() { + for { + r = limit.EnforceRange(r + diff.Poll()) + g = limit.EnforceRange(g + diff.Poll()) + b = limit.EnforceRange(b + diff.Poll()) + // This should be a function in oak to just set color source + // (or texture source) + font.Drawer.Src = image.NewUniform( + color.RGBA{ + uint8(r), + uint8(g), + uint8(b), + 255, + }, + ) + for _, st := range strs { + st.SetString(randomStr(strlen)) + } } - return true - }, + }() + }, }) render.SetDrawStack( render.NewDynamicHeap(), diff --git a/examples/titlescreen-demo/main.go b/examples/titlescreen-demo/main.go index 9cda7718..7a428558 100644 --- a/examples/titlescreen-demo/main.go +++ b/examples/titlescreen-demo/main.go @@ -6,6 +6,7 @@ import ( oak "github.com/oakmound/oak/v3" "github.com/oakmound/oak/v3/entities" + "github.com/oakmound/oak/v3/event" "github.com/oakmound/oak/v3/key" "github.com/oakmound/oak/v3/render" "github.com/oakmound/oak/v3/scene" @@ -53,16 +54,17 @@ func main() { //this time we only center the X axis, otherwise it would overlap titleText center(ctx, instructionText, X) render.Draw(instructionText) - }, Loop: func() bool { - //if the enter key is pressed, go to the next scene - if oak.IsDown(key.Enter) { - return false - } - //exit the program if the q key is pressed - if oak.IsDown(key.Q) { + event.GlobalBind(ctx, key.Down(key.Enter), func(key.Event) event.Response { + // Go to the next scene if enter is pressed. Next scene is the game + ctx.Window.NextScene() + return 0 + }) + event.GlobalBind(ctx, key.Down(key.Q), func(key.Event) event.Response { + // exit the game if q is pressed os.Exit(0) - } - return true + return 0 + }) + }, End: func() (string, *scene.Result) { return "game", nil //set the next scene to "game" }}) @@ -71,7 +73,7 @@ func main() { var player *entities.Moving //define the "game" (it's just a square that can be moved with WASD) - oak.AddScene("game", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("game", scene.Scene{Start: func(ctx *scene.Context) { //create the player, a blue 32x32 square at 100,100 player = entities.NewMoving(100, 100, 32, 32, render.NewColorBox(32, 32, color.RGBA{0, 0, 255, 255}), @@ -84,33 +86,34 @@ func main() { //we draw the text on layer 1 (instead of the default layer 0) //because we want it to show up above the player render.Draw(controlsText, 1) - }, Loop: func() bool { - //if escape is pressed, go to the next scene (titlescreen) - if oak.IsDown(key.Escape) { - return false - } - //controls - if oak.IsDown(key.S) { - //if S is pressed, set the player's vertical speed to 2 (positive == down) - player.Delta.SetY(2) - } else if oak.IsDown(key.W) { - player.Delta.SetY(-2) - } else { - //if the now buttons are pressed for vertical movement, don't move verticaly - player.Delta.SetY(0) - } - - //do the same thing as before, but horizontaly - if oak.IsDown(key.D) { - player.Delta.SetX(2) - } else if oak.IsDown(key.A) { - player.Delta.SetX(-2) - } else { - player.Delta.SetX(0) - } - //apply the player's speed to their position - player.ShiftPos(player.Delta.X(), player.Delta.Y()) - return true + event.GlobalBind(ctx, key.Down(key.Escape), func(key.Event) event.Response { + // Go to the next scene if escape is pressed. Next scene is titlescreen + ctx.Window.NextScene() + return 0 + }) + event.GlobalBind(ctx, event.Enter, func(event.EnterPayload) event.Response { + if oak.IsDown(key.SStr) { + //if S is pressed, set the player's vertical speed to 2 (positive == down) + player.Delta.SetY(2) + } else if oak.IsDown(key.WStr) { + player.Delta.SetY(-2) + } else { + //if the now buttons are pressed for vertical movement, don't move verticaly + player.Delta.SetY(0) + } + + //do the same thing as before, but horizontaly + if oak.IsDown(key.DStr) { + player.Delta.SetX(2) + } else if oak.IsDown(key.AStr) { + player.Delta.SetX(-2) + } else { + player.Delta.SetX(0) + } + //apply the player's speed to their position + player.ShiftPos(player.Delta.X(), player.Delta.Y()) + return 0 + }) }, End: func() (string, *scene.Result) { return "titlescreen", nil //set the next scene to be titlescreen }}) diff --git a/examples/top-down-shooter-tutorial/1-start/start.go b/examples/top-down-shooter-tutorial/1-start/start.go index 46a7a6ec..c2b8834c 100644 --- a/examples/top-down-shooter-tutorial/1-start/start.go +++ b/examples/top-down-shooter-tutorial/1-start/start.go @@ -24,7 +24,7 @@ var ( ) func main() { - oak.AddScene("tds", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("tds", scene.Scene{Start: func(ctx *scene.Context) { playerAlive = true char := entities.NewMoving(100, 100, 32, 32, render.NewColorBox(32, 32, color.RGBA{0, 255, 0, 255}), @@ -33,19 +33,19 @@ func main() { char.Speed = physics.NewVector(5, 5) render.Draw(char.R) - char.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { - char := event.GetEntity(id).(*entities.Moving) + event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { + char.Delta.Zero() - if oak.IsDown(key.W) { + if oak.IsDown(key.WStr) { char.Delta.ShiftY(-char.Speed.Y()) } - if oak.IsDown(key.A) { + if oak.IsDown(key.AStr) { char.Delta.ShiftX(-char.Speed.X()) } - if oak.IsDown(key.S) { + if oak.IsDown(key.SStr) { char.Delta.ShiftY(char.Speed.Y()) } - if oak.IsDown(key.D) { + if oak.IsDown(key.DStr) { char.Delta.ShiftX(char.Speed.X()) } char.ShiftPos(char.Delta.X(), char.Delta.Y()) diff --git a/examples/top-down-shooter-tutorial/2-shooting/shooting.go b/examples/top-down-shooter-tutorial/2-shooting/shooting.go index 9c9d484f..ce9539d4 100644 --- a/examples/top-down-shooter-tutorial/2-shooting/shooting.go +++ b/examples/top-down-shooter-tutorial/2-shooting/shooting.go @@ -21,13 +21,9 @@ const ( Player collision.Label = 2 ) -var ( - playerAlive = true -) - func main() { oak.AddScene("tds", scene.Scene{Start: func(ctx *scene.Context) { - playerAlive = true + char := entities.NewMoving(100, 100, 32, 32, render.NewColorBox(32, 32, color.RGBA{0, 255, 0, 255}), nil, 0, 0) @@ -35,42 +31,38 @@ func main() { char.Speed = physics.NewVector(5, 5) render.Draw(char.R) - char.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { - char := event.GetEntity(id).(*entities.Moving) + event.Bind(ctx, event.Enter, char, func(char *entities.Moving, ev event.EnterPayload) event.Response { + char.Delta.Zero() - if oak.IsDown(key.W) { + if oak.IsDown(key.WStr) { char.Delta.ShiftY(-char.Speed.Y()) } - if oak.IsDown(key.A) { + if oak.IsDown(key.AStr) { char.Delta.ShiftX(-char.Speed.X()) } - if oak.IsDown(key.S) { + if oak.IsDown(key.SStr) { char.Delta.ShiftY(char.Speed.Y()) } - if oak.IsDown(key.D) { + if oak.IsDown(key.DStr) { char.Delta.ShiftX(char.Speed.X()) } char.ShiftPos(char.Delta.X(), char.Delta.Y()) hit := char.HitLabel(Enemy) if hit != nil { - playerAlive = false + ctx.Window.NextScene() } return 0 }) - - char.Bind(mouse.Press, func(id event.CallerID, me interface{}) int { - char := event.GetEntity(id).(*entities.Moving) - mevent := me.(*mouse.Event) + event.Bind(ctx, mouse.Press, char, func(char *entities.Moving, mevent *mouse.Event) event.Response { ctx.DrawForTime( render.NewLine(char.X()+char.W/2, char.Y()+char.H/2, mevent.X(), mevent.Y(), color.RGBA{0, 128, 0, 128}), time.Millisecond*50, 1) + return 0 }) - }, Loop: func() bool { - return playerAlive }}) oak.Init("tds") } diff --git a/examples/top-down-shooter-tutorial/3-enemies/enemies.go b/examples/top-down-shooter-tutorial/3-enemies/enemies.go index 723c1cb2..85580e0e 100644 --- a/examples/top-down-shooter-tutorial/3-enemies/enemies.go +++ b/examples/top-down-shooter-tutorial/3-enemies/enemies.go @@ -25,18 +25,18 @@ const ( ) var ( - playerAlive = true // Vectors are backed by pointers, // so despite this not being a pointer, // this does update according to the player's // position so long as we don't reset // the player's position vector playerPos physics.Vector + + destroy = event.RegisterEvent[event.NoPayload]() ) func main() { oak.AddScene("tds", scene.Scene{Start: func(ctx *scene.Context) { - playerAlive = true char := entities.NewMoving(100, 100, 32, 32, render.NewColorBox(32, 32, color.RGBA{0, 255, 0, 255}), nil, 0, 0) @@ -45,39 +45,36 @@ func main() { playerPos = char.Point.Vector render.Draw(char.R) - char.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { - char := event.GetEntity(id).(*entities.Moving) + event.Bind(ctx, event.Enter, char, func(char *entities.Moving, ev event.EnterPayload) event.Response { + char.Delta.Zero() - if oak.IsDown(key.W) { + if oak.IsDown(key.WStr) { char.Delta.ShiftY(-char.Speed.Y()) } - if oak.IsDown(key.A) { + if oak.IsDown(key.AStr) { char.Delta.ShiftX(-char.Speed.X()) } - if oak.IsDown(key.S) { + if oak.IsDown(key.SStr) { char.Delta.ShiftY(char.Speed.Y()) } - if oak.IsDown(key.D) { + if oak.IsDown(key.DStr) { char.Delta.ShiftX(char.Speed.X()) } char.ShiftPos(char.Delta.X(), char.Delta.Y()) hit := char.HitLabel(Enemy) if hit != nil { - playerAlive = false + ctx.Window.NextScene() } - return 0 }) - char.Bind(mouse.Press, func(id event.CallerID, me interface{}) int { - char := event.GetEntity(id).(*entities.Moving) - mevent := me.(*mouse.Event) + event.Bind(ctx, mouse.Press, char, func(char *entities.Moving, mevent *mouse.Event) event.Response { x := char.X() + char.W/2 y := char.Y() + char.H/2 ray.DefaultCaster.CastDistance = floatgeom.Point2{x, y}.Sub(floatgeom.Point2{mevent.X(), mevent.Y()}).Magnitude() hits := ray.CastTo(floatgeom.Point2{x, y}, floatgeom.Point2{mevent.X(), mevent.Y()}) for _, hit := range hits { - hit.Zone.CID.Trigger("Destroy", nil) + event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, event.NoPayload{}) } ctx.DrawForTime( render.NewLine(x, y, mevent.X(), mevent.Y(), color.RGBA{0, 128, 0, 128}), @@ -86,16 +83,13 @@ func main() { return 0 }) - event.GlobalBind(event.Enter, func(_ event.CallerID, frames interface{}) int { - enterPayload := frames.(event.EnterPayload) + event.GlobalBind(ctx, event.Enter, func(enterPayload event.EnterPayload) event.Response { if enterPayload.FramesElapsed%EnemyRefresh == 0 { go NewEnemy(ctx) } return 0 }) - }, Loop: func() bool { - return playerAlive }}) oak.Init("tds") } @@ -117,21 +111,17 @@ func NewEnemy(ctx *scene.Context) { render.Draw(enemy.R) enemy.UpdateLabel(Enemy) - - enemy.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { - enemy := event.GetEntity(id).(*entities.Solid) + event.Bind(ctx, event.Enter, enemy, func(e *entities.Solid, ev event.EnterPayload) event.Response { // move towards the player - x, y := enemy.GetPos() + x, y := e.GetPos() pt := floatgeom.Point2{x, y} pt2 := floatgeom.Point2{playerPos.X(), playerPos.Y()} delta := pt2.Sub(pt).Normalize().MulConst(EnemySpeed) - enemy.ShiftPos(delta.X(), delta.Y()) + e.ShiftPos(delta.X(), delta.Y()) return 0 }) - - enemy.Bind("Destroy", func(id event.CallerID, _ interface{}) int { - enemy := event.GetEntity(id).(*entities.Solid) - enemy.Destroy() + event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing event.NoPayload) event.Response { + e.Destroy() return 0 }) } diff --git a/examples/top-down-shooter-tutorial/4-sprites/sprites.go b/examples/top-down-shooter-tutorial/4-sprites/sprites.go index 0fd5b783..f0f41ff5 100644 --- a/examples/top-down-shooter-tutorial/4-sprites/sprites.go +++ b/examples/top-down-shooter-tutorial/4-sprites/sprites.go @@ -29,7 +29,6 @@ const ( ) var ( - playerAlive = true // Vectors are backed by pointers, // so despite this not being a pointer, // this does update according to the player's @@ -37,13 +36,15 @@ var ( // the player's position vector playerPos physics.Vector + destroy = event.RegisterEvent[event.NoPayload]() + sheet [][]*render.Sprite ) func main() { oak.AddScene("tds", scene.Scene{Start: func(ctx *scene.Context) { // Initialization - playerAlive = true + sprites, err := render.GetSheet("sheet.png") dlog.ErrorCheck(err) sheet = sprites.ToSprites() @@ -67,25 +68,24 @@ func main() { playerPos = char.Point.Vector render.Draw(char.R, 2) - char.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { - char := event.GetEntity(id).(*entities.Moving) + event.Bind(ctx, event.Enter, char, func(char *entities.Moving, ev event.EnterPayload) event.Response { char.Delta.Zero() - if oak.IsDown(key.W) { + if oak.IsDown(key.WStr) { char.Delta.ShiftY(-char.Speed.Y()) } - if oak.IsDown(key.A) { + if oak.IsDown(key.AStr) { char.Delta.ShiftX(-char.Speed.X()) } - if oak.IsDown(key.S) { + if oak.IsDown(key.SStr) { char.Delta.ShiftY(char.Speed.Y()) } - if oak.IsDown(key.D) { + if oak.IsDown(key.DStr) { char.Delta.ShiftX(char.Speed.X()) } char.ShiftPos(char.Delta.X(), char.Delta.Y()) hit := char.HitLabel(Enemy) if hit != nil { - playerAlive = false + ctx.Window.NextScene() } // update animation @@ -103,15 +103,13 @@ func main() { return 0 }) - char.Bind(mouse.Press, func(id event.CallerID, me interface{}) int { - char := event.GetEntity(id).(*entities.Moving) - mevent := me.(*mouse.Event) + event.Bind(ctx, mouse.Press, char, func(char *entities.Moving, mevent *mouse.Event) event.Response { x := char.X() + char.W/2 y := char.Y() + char.H/2 ray.DefaultCaster.CastDistance = floatgeom.Point2{x, y}.Sub(floatgeom.Point2{mevent.X(), mevent.Y()}).Magnitude() hits := ray.CastTo(floatgeom.Point2{x, y}, floatgeom.Point2{mevent.X(), mevent.Y()}) for _, hit := range hits { - hit.Zone.CID.Trigger("Destroy", nil) + event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, event.NoPayload{}) } ctx.DrawForTime( render.NewLine(x, y, mevent.X(), mevent.Y(), color.RGBA{0, 128, 0, 128}), @@ -121,8 +119,7 @@ func main() { }) // Create enemies periodically - event.GlobalBind(event.Enter, func(_ event.CallerID, frames interface{}) int { - enterPayload := frames.(event.EnterPayload) + event.GlobalBind(ctx, event.Enter, func(enterPayload event.EnterPayload) event.Response { if enterPayload.FramesElapsed%EnemyRefresh == 0 { go NewEnemy(ctx) } @@ -140,8 +137,6 @@ func main() { } } - }, Loop: func() bool { - return playerAlive }}) oak.Init("tds", func(c oak.Config) (oak.Config, error) { @@ -178,8 +173,7 @@ func NewEnemy(ctx *scene.Context) { enemy.UpdateLabel(Enemy) - enemy.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { - enemy := event.GetEntity(id).(*entities.Solid) + event.Bind(ctx, event.Enter, enemy, func(e *entities.Solid, ev event.EnterPayload) event.Response { // move towards the player x, y := enemy.GetPos() pt := floatgeom.Point2{x, y} @@ -201,9 +195,8 @@ func NewEnemy(ctx *scene.Context) { return 0 }) - enemy.Bind("Destroy", func(id event.CallerID, _ interface{}) int { - enemy := event.GetEntity(id).(*entities.Solid) - enemy.Destroy() + event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing event.NoPayload) event.Response { + e.Destroy() return 0 }) } diff --git a/examples/zooming/main.go b/examples/zooming/main.go index c166aaed..80f5fc16 100644 --- a/examples/zooming/main.go +++ b/examples/zooming/main.go @@ -20,7 +20,7 @@ var ( ) func main() { - oak.AddScene("demo", scene.Scene{Start: func(*scene.Context) { + oak.AddScene("demo", scene.Scene{Start: func(ctx *scene.Context) { render.Draw(render.NewText("Controls: Arrow keys", 500, 440)) // Get an image that we will illustrate zooming with later @@ -39,17 +39,17 @@ func main() { render.Draw(zoomer) // To illustrate zooming allow for arrow keys to control the main zoomable renderable. - event.GlobalBind(event.Enter, func(i event.CallerID, _ interface{}) int { - if oak.IsDown(key.UpArrow) { + event.GlobalBind(ctx, key.AnyDown, func(k key.Event) event.Response { + if oak.IsDown(key.UpArrowStr) { zoomOutFactorY *= .98 } - if oak.IsDown(key.DownArrow) { + if oak.IsDown(key.DownArrowStr) { zoomOutFactorY *= 1.02 } - if oak.IsDown(key.RightArrow) { + if oak.IsDown(key.RightArrowStr) { zoomOutFactorX *= 1.02 } - if oak.IsDown(key.LeftArrow) { + if oak.IsDown(key.LeftArrowStr) { zoomOutFactorX *= .98 } From 9e93421a71a9f32337e1c2b03fa3efc4944db2de Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 2 Apr 2022 11:19:35 -0500 Subject: [PATCH 32/41] key: use Code types everywhere --- debugtools/inputviz/keyboard.go | 58 +++-- debugtools/mouse.go | 7 +- default.go | 21 +- entities/x/move/topdown.go | 6 +- examples/collision-demo/main.go | 9 +- examples/piano/main.go | 64 +++-- examples/pong/main.go | 6 +- examples/zooming/main.go | 3 +- inputLoop.go | 12 +- key/events.go | 20 +- key/keycodes.go | 4 +- key/keys.go | 406 +++++++++++--------------------- key/state.go | 16 +- key/state_test.go | 28 +-- 14 files changed, 256 insertions(+), 404 deletions(-) diff --git a/debugtools/inputviz/keyboard.go b/debugtools/inputviz/keyboard.go index e753f8af..d259dd69 100644 --- a/debugtools/inputviz/keyboard.go +++ b/debugtools/inputviz/keyboard.go @@ -11,7 +11,7 @@ import ( ) type KeyboardLayout interface { - KeyRect(k string) floatgeom.Rect2 + KeyRect(k key.Code) floatgeom.Rect2 } type LayoutKey interface { @@ -19,7 +19,7 @@ type LayoutKey interface { } type LayoutPosition struct { - Key string + Key key.Code Gap bool Width float64 Height float64 @@ -36,37 +36,37 @@ func (g gap) Pos() LayoutPosition { } } -type standardKey string +type standardKey key.Code func (s standardKey) Pos() LayoutPosition { return LayoutPosition{ - Key: string(s), + Key: key.Code(s), Width: 1, Height: 1, } } type wideKey struct { - k string + k key.Code w float64 } func (w wideKey) Pos() LayoutPosition { return LayoutPosition{ - Key: string(w.k), + Key: w.k, Width: w.w, Height: 1, } } type tallKey struct { - k string + k key.Code h float64 } func (h tallKey) Pos() LayoutPosition { return LayoutPosition{ - Key: string(h.k), + Key: h.k, Width: 1, Height: h.h, } @@ -74,7 +74,7 @@ func (h tallKey) Pos() LayoutPosition { type LayoutQWERTY struct { Bounds floatgeom.Rect2 - layoutMap map[string]LayoutPosition + layoutMap map[key.Code]LayoutPosition } func (l *LayoutQWERTY) init() { @@ -83,14 +83,14 @@ func (l *LayoutQWERTY) init() { } type sk = standardKey - l.layoutMap = make(map[string]LayoutPosition) + l.layoutMap = make(map[key.Code]LayoutPosition) qwertyRows := [][]LayoutKey{ - {sk(key.EscapeStr), gap(1), sk(key.F1Str), sk(key.F2Str), sk(key.F3Str), sk(key.F4Str), gap(.5), sk(key.F5Str), sk(key.F6Str), sk(key.F7Str), sk(key.F8Str), gap(.5), sk(key.F9Str), sk(key.F10Str), sk(key.F11Str), sk(key.F12Str), gap(2.1), sk(key.Pause)}, - {sk(key.GraveAccentStr), sk(key.OneStr), sk(key.TwoStr), sk(key.ThreeStr), sk(key.FourStr), sk(key.FiveStr), sk(key.SixStr), sk(key.SevenStr), sk(key.EightStr), sk(key.NineStr), sk(key.ZeroStr), sk(key.HyphenMinusStr), sk(key.EqualSignStr), wideKey{key.DeleteBackspaceStr, 2.0}, gap(.1), sk(key.InsertStr), sk(key.HomeStr), sk(key.PageUpStr), gap(.1), sk(key.KeypadNumLockStr), sk(key.KeypadSlashStr), sk(key.KeypadAsteriskStr), sk(key.KeypadHyphenMinus)}, - {wideKey{key.TabStr, 1.5}, sk(key.QStr), sk(key.WStr), sk(key.EStr), sk(key.RStr), sk(key.TStr), sk(key.YStr), sk(key.UStr), sk(key.IStr), sk(key.OStr), sk(key.PStr), sk(key.LeftSquareBracketStr), sk(key.RightSquareBracketStr), wideKey{key.BackslashStr, 1.5}, gap(.1), sk(key.DeleteForwardStr), sk(key.EndStr), sk(key.PageDownStr), gap(.1), sk(key.Keypad7Str), sk(key.Keypad8Str), sk(key.Keypad9Str), tallKey{key.KeypadPlusSignStr, 2}}, - {wideKey{key.CapsLockStr, 1.5}, sk(key.AStr), sk(key.SStr), sk(key.DStr), sk(key.FStr), sk(key.GStr), sk(key.HStr), sk(key.JStr), sk(key.KStr), sk(key.LStr), sk(key.SemicolonStr), sk(key.ApostropheStr), wideKey{key.ReturnEnterStr, 2.5}, gap(3.2), sk(key.Keypad4Str), sk(key.Keypad5Str), sk(key.Keypad6)}, - {wideKey{key.LeftShiftStr, 2.0}, sk(key.ZStr), sk(key.XStr), sk(key.CStr), sk(key.VStr), sk(key.BStr), sk(key.NStr), sk(key.MStr), sk(key.CommaStr), sk(key.FullStopStr), sk(key.SlashStr), wideKey{key.RightShiftStr, 3.0}, gap(1.1), sk(key.UpArrowStr), gap(1.1), sk(key.Keypad1Str), sk(key.Keypad2Str), sk(key.Keypad3Str), tallKey{key.KeypadEnterStr, 2.0}}, - {wideKey{key.LeftControlStr, 1.5}, sk(key.LeftGUIStr), wideKey{key.LeftAltStr, 1.5}, wideKey{key.SpacebarStr, 7.0}, wideKey{key.RightAltStr, 1.5}, sk(key.RightGUIStr), wideKey{key.RightControlStr, 1.5}, gap(.1), sk(key.LeftArrowStr), sk(key.DownArrowStr), sk(key.RightArrowStr), gap(.1), wideKey{key.Keypad0Str, 2.0}, sk(key.KeypadPeriod)}, + {sk(key.Escape), gap(1), sk(key.F1), sk(key.F2), sk(key.F3), sk(key.F4), gap(.5), sk(key.F5), sk(key.F6), sk(key.F7), sk(key.F8), gap(.5), sk(key.F9), sk(key.F10), sk(key.F11), sk(key.F12), gap(2.1), sk(key.Pause)}, + {sk(key.GraveAccent), sk(key.Num1), sk(key.Num2), sk(key.Num3), sk(key.Num4), sk(key.Num5), sk(key.Num6), sk(key.Num7), sk(key.Num8), sk(key.Num9), sk(key.Num0), sk(key.HyphenMinus), sk(key.EqualSign), wideKey{key.DeleteBackspace, 2.0}, gap(.1), sk(key.Insert), sk(key.Home), sk(key.PageUp), gap(.1), sk(key.KeypadNumLock), sk(key.KeypadSlash), sk(key.KeypadAsterisk), sk(key.KeypadHyphenMinus)}, + {wideKey{key.Tab, 1.5}, sk(key.Q), sk(key.W), sk(key.E), sk(key.R), sk(key.T), sk(key.Y), sk(key.U), sk(key.I), sk(key.O), sk(key.P), sk(key.LeftSquareBracket), sk(key.RightSquareBracket), wideKey{key.Backslash, 1.5}, gap(.1), sk(key.DeleteForward), sk(key.End), sk(key.PageDown), gap(.1), sk(key.Keypad7), sk(key.Keypad8), sk(key.Keypad9), tallKey{key.KeypadPlusSign, 2}}, + {wideKey{key.CapsLock, 1.5}, sk(key.A), sk(key.S), sk(key.D), sk(key.F), sk(key.G), sk(key.H), sk(key.J), sk(key.K), sk(key.L), sk(key.Semicolon), sk(key.Apostrophe), wideKey{key.ReturnEnter, 2.5}, gap(3.2), sk(key.Keypad4), sk(key.Keypad5), sk(key.Keypad6)}, + {wideKey{key.LeftShift, 2.0}, sk(key.Z), sk(key.X), sk(key.C), sk(key.V), sk(key.B), sk(key.N), sk(key.M), sk(key.Comma), sk(key.FullStop), sk(key.Slash), wideKey{key.RightShift, 3.0}, gap(1.1), sk(key.UpArrow), gap(1.1), sk(key.Keypad1), sk(key.Keypad2), sk(key.Keypad3), tallKey{key.KeypadEnter, 2.0}}, + {wideKey{key.LeftControl, 1.5}, sk(key.LeftGUI), wideKey{key.LeftAlt, 1.5}, wideKey{key.Spacebar, 7.0}, wideKey{key.RightAlt, 1.5}, sk(key.RightGUI), wideKey{key.RightControl, 1.5}, gap(.1), sk(key.LeftArrow), sk(key.DownArrow), sk(key.RightArrow), gap(.1), wideKey{key.Keypad0, 2.0}, sk(key.KeypadFullStop)}, } rowFloats := []float64{0.0, 1.1, 2.1, 3.1, 4.1, 5.1} for row, cols := range qwertyRows { @@ -98,7 +98,7 @@ func (l *LayoutQWERTY) init() { cf := 0.0 for _, v := range cols { ps := v.Pos() - if ps.Key != "" { + if ps.Key != 0 { l.layoutMap[ps.Key] = LayoutPosition{ Row: rf, Col: cf, @@ -111,7 +111,7 @@ func (l *LayoutQWERTY) init() { } } -func (l *LayoutQWERTY) KeyRect(k string) floatgeom.Rect2 { +func (l *LayoutQWERTY) KeyRect(k key.Code) floatgeom.Rect2 { l.init() pos, ok := l.layoutMap[k] @@ -137,12 +137,12 @@ func (l *LayoutQWERTY) KeyRect(k string) floatgeom.Rect2 { return floatgeom.NewRect2WH(x, y, keyWidth, keyHeight) } -var defaultColors = map[string]color.Color{} +var defaultColors = map[key.Code]color.Color{} type Keyboard struct { Rect floatgeom.Rect2 BaseLayer int - Colors map[string]color.Color + Colors map[key.Code]color.Color KeyboardLayout RenderCharacters bool @@ -151,7 +151,7 @@ type Keyboard struct { event.CallerID ctx *scene.Context - rs map[string]*render.Switch + rs map[key.Code]*render.Switch bindings []event.Binding } @@ -179,9 +179,9 @@ func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { k.Font = render.DefaultFont() } - k.rs = make(map[string]*render.Switch) + k.rs = make(map[key.Code]*render.Switch) - for kv := range key.AllKeys { + for kv, kstr := range key.AllKeys { rect := k.KeyboardLayout.KeyRect(kv) if rect == (floatgeom.Rect2{}) { continue @@ -199,7 +199,7 @@ func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { k.rs[kv] = r if k.RenderCharacters { x, y := rect.Min.X(), rect.Min.Y() - txt := k.Font.NewText(kv, x, y) + txt := k.Font.NewText(kstr, x, y) tw, th := txt.GetDims() xBuffer := rect.W() - float64(tw) yBuffer := rect.H() - float64(th) @@ -222,19 +222,17 @@ func (k *Keyboard) RenderAndListen(ctx *scene.Context, layer int) error { } b1 := event.Bind(ctx, key.AnyDown, k, func(kb *Keyboard, ev key.Event) event.Response { - btn := ev.Code.String()[4:] - if kb.rs[btn] == nil { + if kb.rs[ev.Code] == nil { return 0 } - kb.rs[btn].Set("pressed") + kb.rs[ev.Code].Set("pressed") return 0 }) b2 := event.Bind(ctx, key.AnyUp, k, func(kb *Keyboard, ev key.Event) event.Response { - btn := ev.Code.String()[4:] - if kb.rs[btn] == nil { + if kb.rs[ev.Code] == nil { return 0 } - kb.rs[btn].Set("released") + kb.rs[ev.Code].Set("released") return 0 }) k.bindings = []event.Binding{b1, b2} diff --git a/debugtools/mouse.go b/debugtools/mouse.go index 5b692c7f..2c8aae56 100644 --- a/debugtools/mouse.go +++ b/debugtools/mouse.go @@ -3,15 +3,16 @@ package debugtools import ( "github.com/oakmound/oak/v3/dlog" "github.com/oakmound/oak/v3/event" + "github.com/oakmound/oak/v3/key" "github.com/oakmound/oak/v3/mouse" "github.com/oakmound/oak/v3/scene" ) // DebugMouseRelease will print the position and button pressed of the mouse when the mouse is released, if the given -// key is held down at the time. If no key is given, it will always be printed -func DebugMouseRelease(ctx *scene.Context, k string) { +// key is held down at the time. If 0 is given, it will always be printed +func DebugMouseRelease(ctx *scene.Context, k key.Code) { event.GlobalBind(ctx, mouse.Release, func(mev *mouse.Event) event.Response { - if k == "" || ctx.KeyState.IsDown(k) { + if k == 0 || ctx.KeyState.IsDown(k) { dlog.Info(mev) } return 0 diff --git a/default.go b/default.go index 75384bbe..2ac9a638 100644 --- a/default.go +++ b/default.go @@ -7,6 +7,7 @@ import ( "github.com/oakmound/oak/v3/alg/intgeom" "github.com/oakmound/oak/v3/event" + "github.com/oakmound/oak/v3/key" "github.com/oakmound/oak/v3/render" "github.com/oakmound/oak/v3/scene" ) @@ -37,27 +38,15 @@ func AddScene(name string, sc scene.Scene) error { } // IsDown calls IsDown on the default window. -func IsDown(key string) bool { +func IsDown(k key.Code) bool { initDefaultWindow() - return defaultWindow.IsDown(key) + return defaultWindow.IsDown(k) } // IsHeld calls IsHeld on the default window. -func IsHeld(key string) (bool, time.Duration) { +func IsHeld(k key.Code) (bool, time.Duration) { initDefaultWindow() - return defaultWindow.IsHeld(key) -} - -// SetUp calls SetUp on the default window. -func SetUp(key string) { - initDefaultWindow() - defaultWindow.SetUp(key) -} - -// SetDown calls SetDown on the default window. -func SetDown(key string) { - initDefaultWindow() - defaultWindow.SetDown(key) + return defaultWindow.IsHeld(k) } // SetViewportBounds calls SetViewportBounds on the default window. diff --git a/entities/x/move/topdown.go b/entities/x/move/topdown.go index bfb4a69e..a4a9c6c7 100644 --- a/entities/x/move/topdown.go +++ b/entities/x/move/topdown.go @@ -9,16 +9,16 @@ import ( // WASD moves the given mover based on its speed as W,A,S, and D are pressed func WASD(mvr Mover) { - TopDown(mvr, key.WStr, key.SStr, key.AStr, key.DStr) + TopDown(mvr, key.W, key.S, key.A, key.D) } // Arrows moves the given mover based on its speed as the arrow keys are pressed func Arrows(mvr Mover) { - TopDown(mvr, key.UpArrowStr, key.DownArrowStr, key.LeftArrowStr, key.RightAltStr) + TopDown(mvr, key.UpArrow, key.DownArrow, key.LeftArrow, key.RightAlt) } // TopDown moves the given mover based on its speed as the given keys are pressed -func TopDown(mvr Mover, up, down, left, right string) { +func TopDown(mvr Mover, up, down, left, right key.Code) { delta := mvr.GetDelta() vec := mvr.Vec() spd := mvr.GetSpeed() diff --git a/examples/collision-demo/main.go b/examples/collision-demo/main.go index 571727a3..d5374005 100644 --- a/examples/collision-demo/main.go +++ b/examples/collision-demo/main.go @@ -7,6 +7,7 @@ import ( "github.com/oakmound/oak/v3/collision" "github.com/oakmound/oak/v3/entities" "github.com/oakmound/oak/v3/event" + "github.com/oakmound/oak/v3/key" "github.com/oakmound/oak/v3/render" "github.com/oakmound/oak/v3/scene" ) @@ -33,20 +34,20 @@ func main() { act.R = act.nextR render.Draw(act.R, 0, 1) } - if oak.IsDown("A") { + if oak.IsDown(key.A) { // We could use attachment here to not have to shift both // R and act but that is made more difficult by constantly // changing the act's R act.ShiftX(-3) act.R.ShiftX(-3) - } else if oak.IsDown("D") { + } else if oak.IsDown(key.D) { act.ShiftX(3) act.R.ShiftX(3) } - if oak.IsDown("W") { + if oak.IsDown(key.W) { act.ShiftY(-3) act.R.ShiftY(-3) - } else if oak.IsDown("S") { + } else if oak.IsDown(key.S) { act.ShiftY(3) act.R.ShiftY(3) } diff --git a/examples/piano/main.go b/examples/piano/main.go index a5ab0c02..ef045b8e 100644 --- a/examples/piano/main.go +++ b/examples/piano/main.go @@ -60,7 +60,7 @@ func (kc keyColor) Color() color.RGBA { return color.RGBA{255, 255, 255, 255} } -func newKey(ctx *scene.Context, note synth.Pitch, c keyColor, k string) *entities.Solid { +func newKey(ctx *scene.Context, note synth.Pitch, c keyColor, k key.Code) *entities.Solid { w := c.Width() h := c.Height() clr := c.Color() @@ -93,16 +93,17 @@ func newKey(ctx *scene.Context, note synth.Pitch, c keyColor, k string) *entitie s.Space.Label = labelWhiteKey } mouse.UpdateSpace(s.X(), s.Y(), s.W, s.H, s.Space) - event.GlobalBind(ctx, key.Down(key.AllKeys[k]), func(_ key.Event) event.Response { - if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { + event.GlobalBind(ctx, key.Down(k), func(ev key.Event) event.Response { + // TODO: add helper function for this? + if ev.Modifiers&key.ModShift == key.ModShift { return 0 } playPitch(ctx, note) sw.Set("down") return 0 }) - event.GlobalBind(ctx, key.Up(key.AllKeys[k]), func(_ key.Event) event.Response { - if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { + event.GlobalBind(ctx, key.Up(k), func(ev key.Event) event.Response { + if ev.Modifiers&key.ModShift == key.ModShift { return 0 } releasePitch(note) @@ -129,13 +130,13 @@ type keyDef struct { x float64 } -var keycharOrder = []string{ - "Z", "S", "X", "D", "C", - "V", "G", "B", "H", "N", "J", "M", - key.CommaStr, "L", key.Period, key.SemicolonStr, key.SlashStr, - "Q", "2", "W", "3", "E", "4", "R", - "T", "6", "Y", "7", "U", - "I", "9", "O", "0", "P", key.HyphenMinusStr, key.LeftSquareBracketStr, +var keycharOrder = []key.Code{ + key.Z, key.S, key.X, key.D, key.C, + key.V, key.G, key.B, key.H, key.N, key.J, key.M, + key.Comma, key.L, key.FullStop, key.Semicolon, key.Slash, + key.Q, key.Num2, key.W, key.Num3, key.E, key.Num4, key.R, + key.T, key.Num6, key.Y, key.Num7, key.U, + key.I, key.Num9, key.O, key.Num0, key.P, key.HyphenMinus, key.LeftSquareBracket, } var playLock sync.Mutex @@ -222,30 +223,21 @@ func main() { i++ } // Consider: Adding volume control - event.GlobalBind(ctx, key.Down(key.S), func(_ key.Event) event.Response { - if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { - synthKind = src.SinPCM - } - return 0 - }) - event.GlobalBind(ctx, key.Down(key.W), func(_ key.Event) event.Response { - if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { - synthKind = src.SawPCM - } - return 0 - }) - event.GlobalBind(ctx, key.Down(key.T), func(_ key.Event) event.Response { - if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { - synthKind = src.TrianglePCM - } - return 0 - }) - event.GlobalBind(ctx, key.Down(key.P), func(_ key.Event) event.Response { - if oak.IsDown(key.LeftShiftStr) || oak.IsDown(key.RightShiftStr) { - synthKind = src.PulsePCM(2) - } - return 0 - }) + codeKinds := map[key.Code]func(...synth.Option) (pcm.Reader, error){ + key.S: src.SinPCM, + key.W: src.SawPCM, + key.T: src.TrianglePCM, + key.P: src.PulsePCM(2), + } + for kc, synfn := range codeKinds { + event.GlobalBind(ctx, key.Down(kc), func(ev key.Event) event.Response { + if ev.Modifiers&key.ModShift == key.ModShift { + synthKind = synfn + } + return 0 + }) + } + help1 := render.NewText("Shift+([S]in/[T]ri/[P]ulse/sa[W]) to change wave style", 10, 500) help2 := render.NewText("Keyboard / mouse to play", 10, 520) render.Draw(help1) diff --git a/examples/pong/main.go b/examples/pong/main.go index bcec120f..5ae43bcc 100644 --- a/examples/pong/main.go +++ b/examples/pong/main.go @@ -76,13 +76,13 @@ func newPaddle(ctx *scene.Context, x, y float64, player int) { render.Draw(p.R, 1) p.Space.UpdateLabel(hitPaddle) if player == 1 { - event.Bind(ctx, event.Enter, p, enterPaddle(key.UpArrowStr, key.DownArrowStr)) + event.Bind(ctx, event.Enter, p, enterPaddle(key.UpArrow, key.DownArrow)) } else { - event.Bind(ctx, event.Enter, p, enterPaddle(key.WStr, key.SStr)) + event.Bind(ctx, event.Enter, p, enterPaddle(key.W, key.S)) } } -func enterPaddle(up, down string) func(*entities.Moving, event.EnterPayload) event.Response { +func enterPaddle(up, down key.Code) func(*entities.Moving, event.EnterPayload) event.Response { return func(p *entities.Moving, _ event.EnterPayload) event.Response { p.Delta.SetY(0) if oak.IsDown(up) { diff --git a/examples/zooming/main.go b/examples/zooming/main.go index c166aaed..2f159178 100644 --- a/examples/zooming/main.go +++ b/examples/zooming/main.go @@ -39,7 +39,7 @@ func main() { render.Draw(zoomer) // To illustrate zooming allow for arrow keys to control the main zoomable renderable. - event.GlobalBind(event.Enter, func(i event.CallerID, _ interface{}) int { + event.GlobalBind(event.DefaultBus, event.Enter, func(event.EnterPayload) event.Response { if oak.IsDown(key.UpArrow) { zoomOutFactorY *= .98 } @@ -52,7 +52,6 @@ func main() { if oak.IsDown(key.LeftArrow) { zoomOutFactorX *= .98 } - return 0 }) diff --git a/inputLoop.go b/inputLoop.go index 62d97b50..aa71f860 100644 --- a/inputLoop.go +++ b/inputLoop.go @@ -100,10 +100,9 @@ func (w *Window) inputLoop() { // From the perspective of the event handler this is indistinguishable // from a real keypress. func (w *Window) TriggerKeyDown(e okey.Event) { - k := e.Code.String()[4:] - w.SetDown(k) + w.State.SetDown(e.Code) event.TriggerOn(w.eventHandler, okey.AnyDown, e) - event.TriggerOn(w.eventHandler, okey.Down(okey.Code(e.Code)), e) + event.TriggerOn(w.eventHandler, okey.Down(e.Code), e) } // TriggerKeyUp triggers a software-emulated key release. @@ -111,10 +110,9 @@ func (w *Window) TriggerKeyDown(e okey.Event) { // From the perspective of the event handler this is indistinguishable // from a real key release. func (w *Window) TriggerKeyUp(e okey.Event) { - k := e.Code.String()[4:] - w.SetUp(k) + w.State.SetUp(e.Code) event.TriggerOn(w.eventHandler, okey.AnyUp, e) - event.TriggerOn(w.eventHandler, okey.Up(okey.Code(e.Code)), e) + event.TriggerOn(w.eventHandler, okey.Up(e.Code), e) } // TriggerKeyHeld triggers a software-emulated key hold signal. @@ -123,7 +121,7 @@ func (w *Window) TriggerKeyUp(e okey.Event) { // from a real key hold signal. func (w *Window) TriggerKeyHeld(e okey.Event) { event.TriggerOn(w.eventHandler, okey.AnyHeld, e) - event.TriggerOn(w.eventHandler, okey.Held(okey.Code(e.Code)), e) + event.TriggerOn(w.eventHandler, okey.Held(e.Code), e) } // TriggerMouseEvent triggers a software-emulated mouse event. diff --git a/key/events.go b/key/events.go index 54358f3c..cfbd06f6 100644 --- a/key/events.go +++ b/key/events.go @@ -20,10 +20,24 @@ var ( ) // An Event is sent as the payload for all key bindings. -type Event = key.Event +type Event key.Event -// A code is a unique integer code for a given common key -const CodeA key.Code = key.Code(A) +type Modifiers = key.Modifiers + +const ( + ModShift Modifiers = 1 << 0 + ModControl Modifiers = 1 << 1 + ModAlt Modifiers = 1 << 2 + ModMeta Modifiers = 1 << 3 // called "Command" on OS X +) + +type Direction = key.Direction + +const ( + DirNone Direction = 0 + DirPress Direction = 1 + DirRelease Direction = 2 +) var upEventsLock sync.Mutex var upEvents = map[Code]event.EventID[Event]{} diff --git a/key/keycodes.go b/key/keycodes.go index 8c3ec701..4c06915a 100644 --- a/key/keycodes.go +++ b/key/keycodes.go @@ -1,9 +1,11 @@ package key +import "golang.org/x/mobile/event/key" + // Code is the identity of a key relative to a notional "standard" keyboard. // It is a straight copy of mobile package's key codes cleaned up for ease of binding in oak. // See AllKeys for string mappers. -type Code uint32 +type Code = key.Code const ( Unknown Code = 0 diff --git a/key/keys.go b/key/keys.go index d3c821c0..64bb3d95 100644 --- a/key/keys.go +++ b/key/keys.go @@ -1,277 +1,135 @@ package key -// This lists the keys sent through oak's input events. -// This list is not used internally by oak, but was generated from -// the expected output from x/mobile/key. -// -// These strings are sent as payloads to Key.Down and Key.Up events, -// and through "KeyDown"+$a, "KeyUp"+$a for any $a in the const. -const ( - UnknownStr = "Unknown" - - AStr = "A" - BStr = "B" - CStr = "C" - DStr = "D" - EStr = "E" - FStr = "F" - GStr = "G" - HStr = "H" - IStr = "I" - JStr = "J" - KStr = "K" - LStr = "L" - MStr = "M" - NStr = "N" - OStr = "O" - PStr = "P" - QStr = "Q" - RStr = "R" - SStr = "S" - TStr = "T" - UStr = "U" - VStr = "V" - WStr = "W" - XStr = "X" - YStr = "Y" - ZStr = "Z" - - OneStr = "1" - TwoStr = "2" - ThreeStr = "3" - FourStr = "4" - FiveStr = "5" - SixStr = "6" - SevenStr = "7" - EightStr = "8" - NineStr = "9" - ZeroStr = "0" - - ReturnEnterStr = "ReturnEnter" - Enter = ReturnEnter - EscapeStr = "Escape" - DeleteBackspaceStr = "DeleteBackspace" - TabStr = "Tab" - SpacebarStr = "Spacebar" - HyphenMinusStr = "HyphenMinus" //- - EqualSignStr = "EqualSign" //= - LeftSquareBracketStr = "LeftSquareBracket" //[ - RightSquareBracketStr = "RightSquareBracket" //] - BackslashStr = "Backslash" //\ - SemicolonStr = "Semicolon" //; - ApostropheStr = "Apostrophe" //' - GraveAccentStr = "GraveAccent" //` - CommaStr = "Comma" //, - FullStopStr = "FullStop" //. - Period = "FullStop" - SlashStr = "Slash" /// - CapsLockStr = "CapsLock" - - F1Str = "F1" - F2Str = "F2" - F3Str = "F3" - F4Str = "F4" - F5Str = "F5" - F6Str = "F6" - F7Str = "F7" - F8Str = "F8" - F9Str = "F9" - F10Str = "F10" - F11Str = "F11" - F12Str = "F12" - - PauseStr = "Pause" - InsertStr = "Insert" - HomeStr = "Home" - PageUpStr = "PageUp" - DeleteForwardStr = "DeleteForward" - EndStr = "End" - PageDownStr = "PageDown" - - RightArrowStr = "RightArrow" - LeftArrowStr = "LeftArrow" - DownArrowStr = "DownArrow" - UpArrowStr = "UpArrow" - - KeypadNumLockStr = "KeypadNumLock" - KeypadSlashStr = "KeypadSlash" /// - KeypadAsteriskStr = "KeypadAsterisk" //* - KeypadHyphenMinusStr = "KeypadHyphenMinus" //- - KeypadPlusSignStr = "KeypadPlusSign" //+ - KeypadEnterStr = "KeypadEnter" - Keypad1Str = "Keypad1" - Keypad2Str = "Keypad2" - Keypad3Str = "Keypad3" - Keypad4Str = "Keypad4" - Keypad5Str = "Keypad5" - Keypad6Str = "Keypad6" - Keypad7Str = "Keypad7" - Keypad8Str = "Keypad8" - Keypad9Str = "Keypad9" - Keypad0Str = "Keypad0" - KeypadFullStopStr = "KeypadFullStop" //. - KeypadPeriod = "KeypadFullStop" - KeypadEqualSignStr = "KeypadEqualSign" //= - - F13Str = "F13" - F14Str = "F14" - F15Str = "F15" - F16Str = "F16" - F17Str = "F17" - F18Str = "F18" - F19Str = "F19" - F20Str = "F20" - F21Str = "F21" - F22Str = "F22" - F23Str = "F23" - F24Str = "F24" - - HelpStr = "Help" - - MuteStr = "Mute" - VolumeUpStr = "VolumeUp" - VolumeDownStr = "VolumeDown" - - LeftControlStr = "LeftControl" - LeftShiftStr = "LeftShift" - LeftAltStr = "LeftAlt" - LeftGUIStr = "LeftGUI" - RightControlStr = "RightControl" - RightShiftStr = "RightShift" - RightAltStr = "RightAlt" - RightGUIStr = "RightGUI" -) - -// AllKeys is the set of all defined key codes to their Codes -var AllKeys = map[string]Code{ - UnknownStr: Unknown, - - AStr: A, - BStr: B, - CStr: C, - DStr: D, - EStr: E, - FStr: F, - GStr: G, - HStr: H, - IStr: I, - JStr: J, - KStr: K, - LStr: L, - MStr: M, - NStr: N, - OStr: O, - PStr: P, - QStr: Q, - RStr: R, - SStr: S, - TStr: T, - UStr: U, - VStr: V, - WStr: W, - XStr: X, - YStr: Y, - ZStr: Z, - - OneStr: Num1, - TwoStr: Num2, - ThreeStr: Num3, - FourStr: Num4, - FiveStr: Num5, - SixStr: Num6, - SevenStr: Num7, - EightStr: Num8, - NineStr: Num9, - ZeroStr: Num0, - - ReturnEnterStr: ReturnEnter, - EscapeStr: Escape, - DeleteBackspaceStr: DeleteBackspace, - TabStr: Tab, - SpacebarStr: Spacebar, - HyphenMinusStr: HyphenMinus, - EqualSignStr: EqualSign, - LeftSquareBracketStr: LeftSquareBracket, - RightSquareBracketStr: RightSquareBracket, - BackslashStr: Backslash, - SemicolonStr: Semicolon, - ApostropheStr: Apostrophe, - GraveAccentStr: GraveAccent, - CommaStr: Comma, - FullStopStr: FullStop, - SlashStr: Slash, - CapsLockStr: CapsLock, - - F1Str: F1, - F2Str: F2, - F3Str: F3, - F4Str: F4, - F5Str: F5, - F6Str: F6, - F7Str: F7, - F8Str: F8, - F9Str: F9, - F10Str: F10, - F11Str: F11, - F12Str: F12, - - PauseStr: Pause, - InsertStr: Insert, - HomeStr: Home, - PageUpStr: PageUp, - DeleteForwardStr: DeleteForward, - EndStr: End, - PageDownStr: PageDown, - - RightArrowStr: RightArrow, - LeftArrowStr: LeftArrow, - DownArrowStr: DownArrow, - UpArrowStr: UpArrow, - - KeypadNumLockStr: KeypadNumLock, - KeypadSlashStr: KeypadSlash, - KeypadAsteriskStr: KeypadAsterisk, - KeypadHyphenMinusStr: KeypadHyphenMinus, - KeypadPlusSignStr: KeypadPlusSign, - KeypadEnterStr: KeypadEnter, - Keypad1Str: Keypad1, - Keypad2Str: Keypad2, - Keypad3Str: Keypad3, - Keypad4Str: Keypad4, - Keypad5Str: Keypad5, - Keypad6Str: Keypad6, - Keypad7Str: Keypad7, - Keypad8Str: Keypad8, - Keypad9Str: Keypad9, - Keypad0Str: Keypad0, - KeypadFullStopStr: KeypadFullStop, - KeypadEqualSignStr: KeypadEqualSign, - - F13Str: F13, - F14Str: F14, - F15Str: F15, - F16Str: F16, - F17Str: F17, - F18Str: F18, - F19Str: F19, - F20Str: F20, - F21Str: F21, - F22Str: F22, - F23Str: F23, - F24Str: F24, - - HelpStr: Help, - - MuteStr: Mute, - VolumeUpStr: VolumeUp, - VolumeDownStr: VolumeDown, - - LeftControlStr: LeftControl, - LeftShiftStr: LeftShift, - LeftAltStr: LeftAlt, - LeftGUIStr: LeftGUI, - RightControlStr: RightControl, - RightShiftStr: RightShift, - RightAltStr: RightAlt, - RightGUIStr: RightGUI, +// AllKeys is the set of all defined key codes +var AllKeys = map[Code]string{ + Unknown: "Unknown", + + A: "A", + B: "B", + C: "C", + D: "D", + E: "E", + F: "F", + G: "G", + H: "H", + I: "I", + J: "J", + K: "K", + L: "L", + M: "M", + N: "N", + O: "O", + P: "P", + Q: "Q", + R: "R", + S: "S", + T: "T", + U: "U", + V: "V", + W: "W", + X: "X", + Y: "Y", + Z: "Z", + + Num1: "1", + Num2: "2", + Num3: "3", + Num4: "4", + Num5: "5", + Num6: "6", + Num7: "7", + Num8: "8", + Num9: "9", + Num0: "0", + + ReturnEnter: "ReturnEnter", + Escape: "Escape", + DeleteBackspace: "DeleteBackspace", + Tab: "Tab", + Spacebar: "Spacebar", + HyphenMinus: "HyphenMinus", + EqualSign: "EqualSign", + LeftSquareBracket: "LeftSquareBracket", + RightSquareBracket: "RightSquareBracket", + Backslash: "Backslash", + Semicolon: "Semicolon", + Apostrophe: "Apostrophe", + GraveAccent: "GraveAccent", + Comma: "Comma", + FullStop: "FullStop", + Slash: "Slash", + CapsLock: "CapsLock", + + F1: "F1", + F2: "F2", + F3: "F3", + F4: "F4", + F5: "F5", + F6: "F6", + F7: "F7", + F8: "F8", + F9: "F9", + F10: "F10", + F11: "F11", + F12: "F12", + + Pause: "Pause", + Insert: "Insert", + Home: "Home", + PageUp: "PageUp", + DeleteForward: "DeleteForward", + End: "End", + PageDown: "PageDown", + + RightArrow: "RightArrow", + LeftArrow: "LeftArrow", + DownArrow: "DownArrow", + UpArrow: "UpArrow", + + KeypadNumLock: "KeypadNumLock", + KeypadSlash: "KeypadSlash", + KeypadAsterisk: "KeypadAsterisk", + KeypadHyphenMinus: "KeypadHyphenMinus", + KeypadPlusSign: "KeypadPlusSign", + KeypadEnter: "KeypadEnter", + Keypad1: "Keypad1", + Keypad2: "Keypad2", + Keypad3: "Keypad3", + Keypad4: "Keypad4", + Keypad5: "Keypad5", + Keypad6: "Keypad6", + Keypad7: "Keypad7", + Keypad8: "Keypad8", + Keypad9: "Keypad9", + Keypad0: "Keypad0", + KeypadFullStop: "KeypadFullStop", + KeypadEqualSign: "KeypadEqualSign", + + F13: "F13", + F14: "F14", + F15: "F15", + F16: "F16", + F17: "F17", + F18: "F18", + F19: "F19", + F20: "F20", + F21: "F21", + F22: "F22", + F23: "F23", + F24: "F24", + + Help: "Help", + + Mute: "Mute", + VolumeUp: "VolumeUp", + VolumeDown: "VolumeDown", + + LeftControl: "LeftControl", + LeftShift: "LeftShift", + LeftAlt: "LeftAlt", + LeftGUI: "LeftGUI", + RightControl: "RightControl", + RightShift: "RightShift", + RightAlt: "RightAlt", + RightGUI: "RightGUI", } diff --git a/key/state.go b/key/state.go index 428d955c..0165b0fc 100644 --- a/key/state.go +++ b/key/state.go @@ -8,8 +8,8 @@ import ( // A State tracks what keys of a keyboard are currently pressed and for how long they have been // pressed if they are held down. type State struct { - state map[string]bool - durations map[string]time.Time + state map[Code]bool + durations map[Code]time.Time stateLock sync.RWMutex durationLock sync.RWMutex } @@ -17,8 +17,8 @@ type State struct { // NewState creates a state object for tracking keyboard state. func NewState() State { return State{ - state: make(map[string]bool), - durations: make(map[string]time.Time), + state: make(map[Code]bool), + durations: make(map[Code]time.Time), } } @@ -27,7 +27,7 @@ func NewState() State { // events are sent from the real keyboard and mouse. // Calling this can interrupt real input or cause // unintended behavior and should be done cautiously. -func (ks *State) SetUp(key string) { +func (ks *State) SetUp(key Code) { ks.stateLock.Lock() ks.durationLock.Lock() delete(ks.state, key) @@ -41,7 +41,7 @@ func (ks *State) SetUp(key string) { // events are sent from the real keyboard and mouse. // Calling this can interrupt real input or cause // unintended behavior and should be done cautiously. -func (ks *State) SetDown(key string) { +func (ks *State) SetDown(key Code) { ks.stateLock.Lock() ks.state[key] = true ks.durations[key] = time.Now() @@ -49,7 +49,7 @@ func (ks *State) SetDown(key string) { } // IsDown returns whether a key is held down -func (ks *State) IsDown(key string) (k bool) { +func (ks *State) IsDown(key Code) (k bool) { ks.stateLock.RLock() k = ks.state[key] ks.stateLock.RUnlock() @@ -58,7 +58,7 @@ func (ks *State) IsDown(key string) (k bool) { // IsHeld returns whether a key is held down, and for how long // it has been held. -func (ks *State) IsHeld(key string) (k bool, d time.Duration) { +func (ks *State) IsHeld(key Code) (k bool, d time.Duration) { ks.stateLock.RLock() k = ks.state[key] ks.stateLock.RUnlock() diff --git a/key/state_test.go b/key/state_test.go index 82b11cbb..a9d5cc70 100644 --- a/key/state_test.go +++ b/key/state_test.go @@ -7,29 +7,29 @@ import ( func TestState(t *testing.T) { ks := NewState() - ks.SetDown("Test") - if !ks.IsDown("Test") { - t.Fatalf("test was not set down") + ks.SetDown(A) + if !ks.IsDown(A) { + t.Fatalf("a was not set down") } - ks.SetUp("Test") - if ks.IsDown("Test") { - t.Fatalf("test was not set up") + ks.SetUp(A) + if ks.IsDown(A) { + t.Fatalf("a was not set up") } - ks.SetDown("Test") + ks.SetDown(A) time.Sleep(2 * time.Second) - ok, d := ks.IsHeld("Test") + ok, d := ks.IsHeld(A) if !ok { - t.Fatalf("test was not held down") + t.Fatalf("a was not held down") } if d < 2000*time.Millisecond { - t.Fatalf("test was not held down for sleep length") + t.Fatalf("a was not held down for sleep length") } - ks.SetUp("Test") - ok, d = ks.IsHeld("Test") + ks.SetUp(A) + ok, d = ks.IsHeld(A) if ok { - t.Fatalf("test was not released") + t.Fatalf("a was not released") } if d != 0 { - t.Fatalf("test hold was not reset") + t.Fatalf("a hold was not reset") } } From 9ed67914527ffaf4bd0d03c8b42a785c38840149 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 2 Apr 2022 11:23:45 -0500 Subject: [PATCH 33/41] examples/titlescreen-demo: use new key / events --- examples/titlescreen-demo/main.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/titlescreen-demo/main.go b/examples/titlescreen-demo/main.go index 7a428558..246140b4 100644 --- a/examples/titlescreen-demo/main.go +++ b/examples/titlescreen-demo/main.go @@ -49,12 +49,12 @@ func main() { //tell the draw loop to draw titleText render.Draw(titleText) - //do the same for the text with button instuctions, but this time Y position is not a placeholder (X still is) + //do the same for the text with button instructions, but this time Y position is not a placeholder (X still is) instructionText := render.NewText("press Enter to start, or press Q to quit", 0, float64(ctx.Window.Height()*3/4)) //this time we only center the X axis, otherwise it would overlap titleText center(ctx, instructionText, X) render.Draw(instructionText) - event.GlobalBind(ctx, key.Down(key.Enter), func(key.Event) event.Response { + event.GlobalBind(ctx, key.Down(key.ReturnEnter), func(key.Event) event.Response { // Go to the next scene if enter is pressed. Next scene is the game ctx.Window.NextScene() return 0 @@ -69,7 +69,7 @@ func main() { return "game", nil //set the next scene to "game" }}) - //we declare this here so it can be accesed by the scene start and scene loop + //we declare this here so it can be accessed by the scene start and scene loop var player *entities.Moving //define the "game" (it's just a square that can be moved with WASD) @@ -79,7 +79,7 @@ func main() { render.NewColorBox(32, 32, color.RGBA{0, 0, 255, 255}), nil, 0, 0) //because the player is more than visuals (it has a hitbox, even though we don't use it), - //we have to get the visual part specificaly, and not the whole thing. + //we have to get the visual part specifically, and not the whole thing. render.Draw(player.R) controlsText := render.NewText("WASD to move, ESC to return to titlescreen", 5, 20) @@ -92,20 +92,20 @@ func main() { return 0 }) event.GlobalBind(ctx, event.Enter, func(event.EnterPayload) event.Response { - if oak.IsDown(key.SStr) { + if oak.IsDown(key.S) { //if S is pressed, set the player's vertical speed to 2 (positive == down) player.Delta.SetY(2) - } else if oak.IsDown(key.WStr) { + } else if oak.IsDown(key.W) { player.Delta.SetY(-2) } else { - //if the now buttons are pressed for vertical movement, don't move verticaly + //if the now buttons are pressed for vertical movement, don't move vertically player.Delta.SetY(0) } - //do the same thing as before, but horizontaly - if oak.IsDown(key.DStr) { + //do the same thing as before, but horizontally + if oak.IsDown(key.D) { player.Delta.SetX(2) - } else if oak.IsDown(key.AStr) { + } else if oak.IsDown(key.A) { player.Delta.SetX(-2) } else { player.Delta.SetX(0) From 24d9dd1d4357f48e268b77e1a515558d34aefc46 Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 12:25:06 -0400 Subject: [PATCH 34/41] examples\platformer: Change for strs --- examples/platformer-tutorial/2-moving/moving.go | 4 ++-- examples/platformer-tutorial/3-falling/falling.go | 4 ++-- examples/platformer-tutorial/4-jumping/jumping.go | 6 +++--- .../5-correct-jumping/correct-jumping.go | 6 +++--- examples/platformer-tutorial/6-complete/complete.go | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/platformer-tutorial/2-moving/moving.go b/examples/platformer-tutorial/2-moving/moving.go index 07d27096..d025bf3a 100644 --- a/examples/platformer-tutorial/2-moving/moving.go +++ b/examples/platformer-tutorial/2-moving/moving.go @@ -26,10 +26,10 @@ func main() { char.Speed = physics.NewVector(3, 3) event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { // Move left and right with A and D - if oak.IsDown(key.AStr) { + if oak.IsDown(key.A) { c.ShiftX(-c.Speed.X()) } - if oak.IsDown(key.DStr) { + if oak.IsDown(key.D) { c.ShiftX(c.Speed.X()) } return 0 diff --git a/examples/platformer-tutorial/3-falling/falling.go b/examples/platformer-tutorial/3-falling/falling.go index beabdd44..a6968536 100644 --- a/examples/platformer-tutorial/3-falling/falling.go +++ b/examples/platformer-tutorial/3-falling/falling.go @@ -39,10 +39,10 @@ func main() { event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { // Move left and right with A and D - if oak.IsDown(key.AStr) { + if oak.IsDown(key.A) { c.ShiftX(-c.Speed.X()) } - if oak.IsDown(key.DStr) { + if oak.IsDown(key.D) { c.ShiftX(c.Speed.X()) } hit := c.HitLabel(Ground) diff --git a/examples/platformer-tutorial/4-jumping/jumping.go b/examples/platformer-tutorial/4-jumping/jumping.go index 41c950a8..c9049469 100644 --- a/examples/platformer-tutorial/4-jumping/jumping.go +++ b/examples/platformer-tutorial/4-jumping/jumping.go @@ -38,10 +38,10 @@ func main() { event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { // Move left and right with A and D - if oak.IsDown(key.AStr) { + if oak.IsDown(key.A) { c.ShiftX(-c.Speed.X()) } - if oak.IsDown(key.DStr) { + if oak.IsDown(key.D) { c.ShiftX(c.Speed.X()) } hit := collision.HitLabel(c.Space, Ground) @@ -51,7 +51,7 @@ func main() { } else { c.Delta.SetY(0) // Jump with Space - if oak.IsDown(key.SpacebarStr) { + if oak.IsDown(key.Spacebar) { c.Delta.ShiftY(-c.Speed.Y()) } } diff --git a/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go b/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go index 4d2c381e..92040f79 100644 --- a/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go +++ b/examples/platformer-tutorial/5-correct-jumping/correct-jumping.go @@ -38,10 +38,10 @@ func main() { event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { // Move left and right with A and D - if oak.IsDown(key.AStr) { + if oak.IsDown(key.A) { char.ShiftX(-char.Speed.X()) } - if oak.IsDown(key.DStr) { + if oak.IsDown(key.D) { char.ShiftX(char.Speed.X()) } oldY := char.Y() @@ -55,7 +55,7 @@ func main() { char.SetY(hit.Y() - char.H) char.Delta.SetY(0) // Jump with Space - if oak.IsDown(key.SpacebarStr) { + if oak.IsDown(key.Spacebar) { char.Delta.ShiftY(-char.Speed.Y()) } } else { diff --git a/examples/platformer-tutorial/6-complete/complete.go b/examples/platformer-tutorial/6-complete/complete.go index 12a5b5d7..42f17f7a 100644 --- a/examples/platformer-tutorial/6-complete/complete.go +++ b/examples/platformer-tutorial/6-complete/complete.go @@ -41,9 +41,9 @@ func main() { event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { // Move left and right with A and D - if oak.IsDown(key.AStr) { + if oak.IsDown(key.A) { char.Delta.SetX(-char.Speed.X()) - } else if oak.IsDown(key.DStr) { + } else if oak.IsDown(key.D) { char.Delta.SetX(char.Speed.X()) } else { char.Delta.SetX(0) @@ -63,7 +63,7 @@ func main() { // Stop falling char.Delta.SetY(0) // Jump with Space when on the ground - if oak.IsDown(key.SpacebarStr) { + if oak.IsDown(key.Spacebar) { char.Delta.ShiftY(-char.Speed.Y()) } aboveGround = true From 435da4eebc75c7597db39b2421b0f8e6ceeaa99c Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 12:26:53 -0400 Subject: [PATCH 35/41] examples\sprite: str changes --- examples/sprite-demo/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/sprite-demo/main.go b/examples/sprite-demo/main.go index ac82350c..872a3e53 100644 --- a/examples/sprite-demo/main.go +++ b/examples/sprite-demo/main.go @@ -10,6 +10,7 @@ import ( oak "github.com/oakmound/oak/v3" "github.com/oakmound/oak/v3/entities" "github.com/oakmound/oak/v3/event" + "github.com/oakmound/oak/v3/key" "github.com/oakmound/oak/v3/render" "github.com/oakmound/oak/v3/render/mod" "github.com/oakmound/oak/v3/scene" @@ -38,7 +39,7 @@ func main() { NewGopher(ctx, layer) layer++ event.GlobalBind(ctx, event.Enter, func(ev event.EnterPayload) event.Response { - if oak.IsDown("K") { + if oak.IsDown(key.K) { NewGopher(ctx, layer) layer++ } From d9b3e4b46a232b345ebd50962012f64b4d1de796 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 2 Apr 2022 11:34:09 -0500 Subject: [PATCH 36/41] examples/top-down-shooter-tutorial: finish fixing for new events and keys --- .../1-start/start.go | 8 ++-- .../2-shooting/shooting.go | 8 ++-- .../3-enemies/enemies.go | 8 ++-- .../4-sprites/sprites.go | 8 ++-- .../5-viewport/viewport.go | 32 +++++-------- .../6-performance/performance.go | 45 +++++++------------ 6 files changed, 45 insertions(+), 64 deletions(-) diff --git a/examples/top-down-shooter-tutorial/1-start/start.go b/examples/top-down-shooter-tutorial/1-start/start.go index c2b8834c..126cf2e4 100644 --- a/examples/top-down-shooter-tutorial/1-start/start.go +++ b/examples/top-down-shooter-tutorial/1-start/start.go @@ -36,16 +36,16 @@ func main() { event.Bind(ctx, event.Enter, char, func(c *entities.Moving, ev event.EnterPayload) event.Response { char.Delta.Zero() - if oak.IsDown(key.WStr) { + if oak.IsDown(key.W) { char.Delta.ShiftY(-char.Speed.Y()) } - if oak.IsDown(key.AStr) { + if oak.IsDown(key.A) { char.Delta.ShiftX(-char.Speed.X()) } - if oak.IsDown(key.SStr) { + if oak.IsDown(key.S) { char.Delta.ShiftY(char.Speed.Y()) } - if oak.IsDown(key.DStr) { + if oak.IsDown(key.D) { char.Delta.ShiftX(char.Speed.X()) } char.ShiftPos(char.Delta.X(), char.Delta.Y()) diff --git a/examples/top-down-shooter-tutorial/2-shooting/shooting.go b/examples/top-down-shooter-tutorial/2-shooting/shooting.go index ce9539d4..89844bb0 100644 --- a/examples/top-down-shooter-tutorial/2-shooting/shooting.go +++ b/examples/top-down-shooter-tutorial/2-shooting/shooting.go @@ -34,16 +34,16 @@ func main() { event.Bind(ctx, event.Enter, char, func(char *entities.Moving, ev event.EnterPayload) event.Response { char.Delta.Zero() - if oak.IsDown(key.WStr) { + if oak.IsDown(key.W) { char.Delta.ShiftY(-char.Speed.Y()) } - if oak.IsDown(key.AStr) { + if oak.IsDown(key.A) { char.Delta.ShiftX(-char.Speed.X()) } - if oak.IsDown(key.SStr) { + if oak.IsDown(key.S) { char.Delta.ShiftY(char.Speed.Y()) } - if oak.IsDown(key.DStr) { + if oak.IsDown(key.D) { char.Delta.ShiftX(char.Speed.X()) } char.ShiftPos(char.Delta.X(), char.Delta.Y()) diff --git a/examples/top-down-shooter-tutorial/3-enemies/enemies.go b/examples/top-down-shooter-tutorial/3-enemies/enemies.go index 85580e0e..5117255d 100644 --- a/examples/top-down-shooter-tutorial/3-enemies/enemies.go +++ b/examples/top-down-shooter-tutorial/3-enemies/enemies.go @@ -48,16 +48,16 @@ func main() { event.Bind(ctx, event.Enter, char, func(char *entities.Moving, ev event.EnterPayload) event.Response { char.Delta.Zero() - if oak.IsDown(key.WStr) { + if oak.IsDown(key.W) { char.Delta.ShiftY(-char.Speed.Y()) } - if oak.IsDown(key.AStr) { + if oak.IsDown(key.A) { char.Delta.ShiftX(-char.Speed.X()) } - if oak.IsDown(key.SStr) { + if oak.IsDown(key.S) { char.Delta.ShiftY(char.Speed.Y()) } - if oak.IsDown(key.DStr) { + if oak.IsDown(key.D) { char.Delta.ShiftX(char.Speed.X()) } char.ShiftPos(char.Delta.X(), char.Delta.Y()) diff --git a/examples/top-down-shooter-tutorial/4-sprites/sprites.go b/examples/top-down-shooter-tutorial/4-sprites/sprites.go index f0f41ff5..ee8ea146 100644 --- a/examples/top-down-shooter-tutorial/4-sprites/sprites.go +++ b/examples/top-down-shooter-tutorial/4-sprites/sprites.go @@ -70,16 +70,16 @@ func main() { event.Bind(ctx, event.Enter, char, func(char *entities.Moving, ev event.EnterPayload) event.Response { char.Delta.Zero() - if oak.IsDown(key.WStr) { + if oak.IsDown(key.W) { char.Delta.ShiftY(-char.Speed.Y()) } - if oak.IsDown(key.AStr) { + if oak.IsDown(key.A) { char.Delta.ShiftX(-char.Speed.X()) } - if oak.IsDown(key.SStr) { + if oak.IsDown(key.S) { char.Delta.ShiftY(char.Speed.Y()) } - if oak.IsDown(key.DStr) { + if oak.IsDown(key.D) { char.Delta.ShiftX(char.Speed.X()) } char.ShiftPos(char.Delta.X(), char.Delta.Y()) diff --git a/examples/top-down-shooter-tutorial/5-viewport/viewport.go b/examples/top-down-shooter-tutorial/5-viewport/viewport.go index 5adae2db..5126c98e 100644 --- a/examples/top-down-shooter-tutorial/5-viewport/viewport.go +++ b/examples/top-down-shooter-tutorial/5-viewport/viewport.go @@ -29,7 +29,6 @@ const ( ) var ( - playerAlive = true // Vectors are backed by pointers, // so despite this not being a pointer, // this does update according to the player's @@ -37,6 +36,8 @@ var ( // the player's position vector playerPos physics.Vector + destroy = event.RegisterEvent[event.NoPayload]() + sheet [][]*render.Sprite ) @@ -49,7 +50,6 @@ func main() { oak.AddScene("tds", scene.Scene{Start: func(ctx *scene.Context) { // Initialization - playerAlive = true sprites, err := render.GetSheet("sheet.png") dlog.ErrorCheck(err) sheet = sprites.ToSprites() @@ -75,8 +75,7 @@ func main() { playerPos = char.Point.Vector render.Draw(char.R, 2) - char.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { - char := event.GetEntity(id).(*entities.Moving) + event.Bind(ctx, event.Enter, char, func(char *entities.Moving, ev event.EnterPayload) event.Response { char.Delta.Zero() if oak.IsDown(key.W) { char.Delta.ShiftY(-char.Speed.Y()) @@ -108,7 +107,7 @@ func main() { ) hit := char.HitLabel(Enemy) if hit != nil { - playerAlive = false + ctx.Window.NextScene() } // update animation @@ -126,9 +125,7 @@ func main() { return 0 }) - char.Bind(mouse.Press, func(id event.CallerID, me interface{}) int { - char := event.GetEntity(id).(*entities.Moving) - mevent := me.(*mouse.Event) + event.Bind(ctx, mouse.Press, char, func(char *entities.Moving, mevent *mouse.Event) event.Response { x := char.X() + char.W/2 y := char.Y() + char.H/2 vp := ctx.Window.Viewport() @@ -137,7 +134,7 @@ func main() { ray.DefaultCaster.CastDistance = floatgeom.Point2{x, y}.Sub(floatgeom.Point2{mx, my}).Magnitude() hits := ray.CastTo(floatgeom.Point2{x, y}, floatgeom.Point2{mx, my}) for _, hit := range hits { - hit.Zone.CID.Trigger("Destroy", nil) + event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, event.NoPayload{}) } ctx.DrawForTime( render.NewLine(x, y, mx, my, color.RGBA{0, 128, 0, 128}), @@ -147,10 +144,9 @@ func main() { }) // Create enemies periodically - event.GlobalBind(event.Enter, func(_ event.CallerID, frames interface{}) int { - enterPayload := frames.(event.EnterPayload) + event.GlobalBind(ctx, event.Enter, func(enterPayload event.EnterPayload) event.Response { if enterPayload.FramesElapsed%EnemyRefresh == 0 { - go NewEnemy() + go NewEnemy(ctx) } return 0 }) @@ -166,8 +162,6 @@ func main() { } } - }, Loop: func() bool { - return playerAlive }}) oak.Init("tds", func(c oak.Config) (oak.Config, error) { @@ -184,7 +178,7 @@ const ( ) // NewEnemy creates an enemy for a top down shooter -func NewEnemy() { +func NewEnemy(ctx *scene.Context) { x, y := enemyPos() enemyFrame := sheet[0][0].Copy() @@ -200,8 +194,7 @@ func NewEnemy() { enemy.UpdateLabel(Enemy) - enemy.Bind(event.Enter, func(id event.CallerID, _ interface{}) int { - enemy := event.GetEntity(id).(*entities.Solid) + event.Bind(ctx, event.Enter, enemy, func(e *entities.Solid, ev event.EnterPayload) event.Response { // move towards the player x, y := enemy.GetPos() pt := floatgeom.Point2{x, y} @@ -223,9 +216,8 @@ func NewEnemy() { return 0 }) - enemy.Bind("Destroy", func(id event.CallerID, _ interface{}) int { - enemy := event.GetEntity(id).(*entities.Solid) - enemy.Destroy() + event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing event.NoPayload) event.Response { + e.Destroy() return 0 }) } diff --git a/examples/top-down-shooter-tutorial/6-performance/performance.go b/examples/top-down-shooter-tutorial/6-performance/performance.go index 112b89cc..3d2afd77 100644 --- a/examples/top-down-shooter-tutorial/6-performance/performance.go +++ b/examples/top-down-shooter-tutorial/6-performance/performance.go @@ -28,7 +28,6 @@ const ( ) var ( - playerAlive = true // Vectors are backed by pointers, // so despite this not being a pointer, // this does update according to the player's @@ -36,6 +35,8 @@ var ( // the player's position vector playerPos physics.Vector + destroy = event.RegisterEvent[event.NoPayload]() + sheet [][]*render.Sprite ) @@ -52,7 +53,6 @@ func main() { // render.Draw(debugtools.NewThickRTree(ctx, collision.DefaultTree, 5), 2, 3) // Initialization - playerAlive = true sprites, err := render.GetSheet("sheet.png") dlog.ErrorCheck(err) sheet = sprites.ToSprites() @@ -81,21 +81,18 @@ func main() { float64(ctx.Window.Height()) / 2, } - char.Bind(event.Enter, func(id event.CallerID, payload interface{}) int { - char := event.GetEntity(id).(*entities.Moving) - - enterPayload := payload.(event.EnterPayload) + event.Bind(ctx, event.Enter, char, func(char *entities.Moving, ev event.EnterPayload) event.Response { if oak.IsDown(key.W) { - char.Delta.ShiftY(-char.Speed.Y() * enterPayload.TickPercent) + char.Delta.ShiftY(-char.Speed.Y() * ev.TickPercent) } if oak.IsDown(key.A) { - char.Delta.ShiftX(-char.Speed.X() * enterPayload.TickPercent) + char.Delta.ShiftX(-char.Speed.X() * ev.TickPercent) } if oak.IsDown(key.S) { - char.Delta.ShiftY(char.Speed.Y() * enterPayload.TickPercent) + char.Delta.ShiftY(char.Speed.Y() * ev.TickPercent) } if oak.IsDown(key.D) { - char.Delta.ShiftX(char.Speed.X() * enterPayload.TickPercent) + char.Delta.ShiftX(char.Speed.X() * ev.TickPercent) } ctx.Window.(*oak.Window).DoBetweenDraws(func() { char.ShiftPos(char.Delta.X(), char.Delta.Y()) @@ -119,7 +116,7 @@ func main() { hit := char.HitLabel(Enemy) if hit != nil { - playerAlive = false + ctx.Window.NextScene() } // update animation @@ -137,9 +134,7 @@ func main() { return 0 }) - char.Bind(mouse.Press, func(id event.CallerID, me interface{}) int { - char := event.GetEntity(id).(*entities.Moving) - mevent := me.(*mouse.Event) + event.Bind(ctx, mouse.Press, char, func(char *entities.Moving, mevent *mouse.Event) event.Response { x := char.X() + char.W/2 y := char.Y() + char.H/2 vp := ctx.Window.Viewport() @@ -148,7 +143,7 @@ func main() { ray.DefaultCaster.CastDistance = floatgeom.Point2{x, y}.Sub(floatgeom.Point2{mx, my}).Magnitude() hits := ray.CastTo(floatgeom.Point2{x, y}, floatgeom.Point2{mx, my}) for _, hit := range hits { - hit.Zone.CID.Trigger("Destroy", nil) + event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, event.NoPayload{}) } ctx.DrawForTime( render.NewLine(x, y, mx, my, color.RGBA{0, 128, 0, 128}), @@ -158,10 +153,9 @@ func main() { }) // Create enemies periodically - event.GlobalBind(event.Enter, func(_ event.CallerID, frames interface{}) int { - enterPayload := frames.(event.EnterPayload) + event.GlobalBind(ctx, event.Enter, func(enterPayload event.EnterPayload) event.Response { if enterPayload.FramesElapsed%EnemyRefresh == 0 { - go NewEnemy() + go NewEnemy(ctx) } return 0 }) @@ -177,8 +171,6 @@ func main() { } } - }, Loop: func() bool { - return playerAlive }}) render.SetDrawStack( @@ -206,7 +198,7 @@ const ( ) // NewEnemy creates an enemy for a top down shooter -func NewEnemy() { +func NewEnemy(ctx *scene.Context) { x, y := enemyPos() enemyFrame := sheet[0][0].Copy() @@ -222,14 +214,12 @@ func NewEnemy() { enemy.UpdateLabel(Enemy) - enemy.Bind(event.Enter, func(id event.CallerID, payload interface{}) int { - enemy := event.GetEntity(id).(*entities.Solid) - enterPayload := payload.(event.EnterPayload) + event.Bind(ctx, event.Enter, enemy, func(e *entities.Solid, ev event.EnterPayload) event.Response { // move towards the player x, y := enemy.GetPos() pt := floatgeom.Point2{x, y} pt2 := floatgeom.Point2{playerPos.X(), playerPos.Y()} - delta := pt2.Sub(pt).Normalize().MulConst(EnemySpeed * enterPayload.TickPercent) + delta := pt2.Sub(pt).Normalize().MulConst(EnemySpeed * ev.TickPercent) enemy.ShiftPos(delta.X(), delta.Y()) // update animation @@ -246,9 +236,8 @@ func NewEnemy() { return 0 }) - enemy.Bind("Destroy", func(id event.CallerID, _ interface{}) int { - enemy := event.GetEntity(id).(*entities.Solid) - enemy.Destroy() + event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing event.NoPayload) event.Response { + e.Destroy() return 0 }) } From 4dfa4ce4d5f1b9a012732346f89062a7523a4f95 Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 12:53:35 -0400 Subject: [PATCH 37/41] examples/slides: Update for new event format --- examples/slide/show/slide.go | 36 ++++++++++++++++-------- examples/slide/show/static/basicSlide.go | 26 ++++++++++------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/examples/slide/show/slide.go b/examples/slide/show/slide.go index 90d9af17..2fdade72 100644 --- a/examples/slide/show/slide.go +++ b/examples/slide/show/slide.go @@ -9,12 +9,13 @@ import ( oak "github.com/oakmound/oak/v3" "github.com/oakmound/oak/v3/debugstream" "github.com/oakmound/oak/v3/event" + "github.com/oakmound/oak/v3/key" "github.com/oakmound/oak/v3/render" "github.com/oakmound/oak/v3/scene" ) type Slide interface { - Init() + Init(*scene.Context) Continue() bool Prev() bool Transition() scene.Transition @@ -59,14 +60,19 @@ func Start(width, height int, slides ...Slide) { i := i sl := sl oak.AddScene("slide"+strconv.Itoa(i), scene.Scene{ - Start: func(*scene.Context) { sl.Init() }, - Loop: func() bool { - cont := sl.Continue() && !skip - // This should be disable-able - if !cont { + Start: func(ctx *scene.Context) { + + sl.Init(ctx) + event.GlobalBind(ctx, event.Enter, func(event.EnterPayload) event.Response { + cont := sl.Continue() && !skip oak.SetLoadingRenderable(render.NewSprite(0, 0, oak.ScreenShot())) - } - return cont + if !cont { + ctx.Window.NextScene() + return event.ResponseUnbindThisBinding + } + return 0 + }) + }, End: func() (string, *scene.Result) { fmt.Println("ending") @@ -75,6 +81,7 @@ func Start(width, height int, slides ...Slide) { return "slide" + skipTo, slideResult(sl) } if sl.Prev() { + fmt.Println("Prev slide requested from", i) if i > 0 { return "slide" + strconv.Itoa(i-1), slideResult(sl) } @@ -102,13 +109,18 @@ func Start(width, height int, slides ...Slide) { float64(ctx.Window.Height()-50), ), ) - event.GlobalBind("KeyDownSpacebar", func(event.CallerID, interface{}) int { + event.GlobalBind(ctx, key.Down(key.Spacebar), func(key.Event) event.Response { reset = true return 0 }) - }, - Loop: func() bool { - return !reset + + event.GlobalBind(ctx, event.Enter, func(event.EnterPayload) event.Response { + if !reset { + ctx.Window.NextScene() + return event.ResponseUnbindThisBinding + } + return 0 + }) }, End: func() (string, *scene.Result) { oak.SetColorBackground(oldBackground) diff --git a/examples/slide/show/static/basicSlide.go b/examples/slide/show/static/basicSlide.go index 29c930ba..3b2d01ce 100644 --- a/examples/slide/show/static/basicSlide.go +++ b/examples/slide/show/static/basicSlide.go @@ -6,39 +6,45 @@ import ( oak "github.com/oakmound/oak/v3" "github.com/oakmound/oak/v3/event" + "github.com/oakmound/oak/v3/key" + "github.com/oakmound/oak/v3/mouse" "github.com/oakmound/oak/v3/render" "github.com/oakmound/oak/v3/scene" ) type Slide struct { Rs *render.CompositeR - ContinueKey string - PrevKey string + ContinueKey key.Code + PrevKey key.Code transition scene.Transition cont bool prev bool OnClick func() } -func (ss *Slide) Init() { +func (ss *Slide) Init(ctx *scene.Context) { oak.SetFullScreen(true) render.Draw(ss.Rs, 0) - event.GlobalBind("KeyUp"+ss.ContinueKey, func(event.CallerID, interface{}) int { + + event.GlobalBind(ctx, key.Up(ss.ContinueKey), func(key.Event) event.Response { + fmt.Println("continue key pressed") ss.cont = true return 0 }) - event.GlobalBind("KeyUp"+ss.PrevKey, func(event.CallerID, interface{}) int { + + event.GlobalBind(ctx, key.Up(ss.PrevKey), func(key.Event) event.Response { fmt.Println("prev key pressed") ss.prev = true return 0 }) - event.GlobalBind("KeyUpEscape", func(event.CallerID, interface{}) int { + + event.GlobalBind(ctx, key.Up(key.Escape), func(key.Event) event.Response { os.Exit(0) return 0 }) if ss.OnClick != nil { - event.GlobalBind("MousePress", func(event.CallerID, interface{}) int { + event.GlobalBind(ctx, mouse.Press, func(*mouse.Event) event.Response { ss.OnClick() return 0 }) @@ -69,8 +75,8 @@ func (ss *Slide) Transition() scene.Transition { func NewSlide(rs ...render.Renderable) *Slide { return &Slide{ Rs: render.NewCompositeR(rs...), - ContinueKey: "RightArrow", - PrevKey: "LeftArrow", + ContinueKey: key.RightArrow, + PrevKey: key.LeftArrow, } } @@ -88,7 +94,7 @@ func Background(r render.Modifiable) SlideOption { } } -func ControlKeys(cont, prev string) SlideOption { +func ControlKeys(cont, prev key.Code) SlideOption { return func(s *Slide) *Slide { s.ContinueKey = cont s.PrevKey = prev From 8ed533888613faa3d8015b087fec23e9389dde4d Mon Sep 17 00:00:00 2001 From: implausiblyfun Date: Sat, 2 Apr 2022 12:53:54 -0400 Subject: [PATCH 38/41] .github: Update workflows for go 1.18 --- .github/workflows/go.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8059106a..a7d1071f 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -6,10 +6,10 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - - name: Set up Go 1.17 + - name: Set up Go 1.18 uses: actions/setup-go@v1 with: - go-version: 1.17 + go-version: 1.18 id: go - name: Check out code into the Go module directory @@ -27,10 +27,10 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up Go 1.17 + - name: Set up Go 1.18 uses: actions/setup-go@v1 with: - go-version: 1.17 + go-version: 1.18 id: go - name: Check out code into the Go module directory From 8c9ca67b57554dd1324a109df12324f073c43e94 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 2 Apr 2022 11:56:33 -0500 Subject: [PATCH 39/41] examples: remove event.NoPayload usages --- examples/top-down-shooter-tutorial/3-enemies/enemies.go | 6 +++--- examples/top-down-shooter-tutorial/4-sprites/sprites.go | 6 +++--- examples/top-down-shooter-tutorial/5-viewport/viewport.go | 6 +++--- .../top-down-shooter-tutorial/6-performance/performance.go | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/top-down-shooter-tutorial/3-enemies/enemies.go b/examples/top-down-shooter-tutorial/3-enemies/enemies.go index 5117255d..f34b0599 100644 --- a/examples/top-down-shooter-tutorial/3-enemies/enemies.go +++ b/examples/top-down-shooter-tutorial/3-enemies/enemies.go @@ -32,7 +32,7 @@ var ( // the player's position vector playerPos physics.Vector - destroy = event.RegisterEvent[event.NoPayload]() + destroy = event.RegisterEvent[struct{}]() ) func main() { @@ -74,7 +74,7 @@ func main() { ray.DefaultCaster.CastDistance = floatgeom.Point2{x, y}.Sub(floatgeom.Point2{mevent.X(), mevent.Y()}).Magnitude() hits := ray.CastTo(floatgeom.Point2{x, y}, floatgeom.Point2{mevent.X(), mevent.Y()}) for _, hit := range hits { - event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, event.NoPayload{}) + event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, struct{}{}) } ctx.DrawForTime( render.NewLine(x, y, mevent.X(), mevent.Y(), color.RGBA{0, 128, 0, 128}), @@ -120,7 +120,7 @@ func NewEnemy(ctx *scene.Context) { e.ShiftPos(delta.X(), delta.Y()) return 0 }) - event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing event.NoPayload) event.Response { + event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing struct{}) event.Response { e.Destroy() return 0 }) diff --git a/examples/top-down-shooter-tutorial/4-sprites/sprites.go b/examples/top-down-shooter-tutorial/4-sprites/sprites.go index ee8ea146..933701ed 100644 --- a/examples/top-down-shooter-tutorial/4-sprites/sprites.go +++ b/examples/top-down-shooter-tutorial/4-sprites/sprites.go @@ -36,7 +36,7 @@ var ( // the player's position vector playerPos physics.Vector - destroy = event.RegisterEvent[event.NoPayload]() + destroy = event.RegisterEvent[struct{}]() sheet [][]*render.Sprite ) @@ -109,7 +109,7 @@ func main() { ray.DefaultCaster.CastDistance = floatgeom.Point2{x, y}.Sub(floatgeom.Point2{mevent.X(), mevent.Y()}).Magnitude() hits := ray.CastTo(floatgeom.Point2{x, y}, floatgeom.Point2{mevent.X(), mevent.Y()}) for _, hit := range hits { - event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, event.NoPayload{}) + event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, struct{}{}) } ctx.DrawForTime( render.NewLine(x, y, mevent.X(), mevent.Y(), color.RGBA{0, 128, 0, 128}), @@ -195,7 +195,7 @@ func NewEnemy(ctx *scene.Context) { return 0 }) - event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing event.NoPayload) event.Response { + event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing struct{}) event.Response { e.Destroy() return 0 }) diff --git a/examples/top-down-shooter-tutorial/5-viewport/viewport.go b/examples/top-down-shooter-tutorial/5-viewport/viewport.go index 5126c98e..4900b62c 100644 --- a/examples/top-down-shooter-tutorial/5-viewport/viewport.go +++ b/examples/top-down-shooter-tutorial/5-viewport/viewport.go @@ -36,7 +36,7 @@ var ( // the player's position vector playerPos physics.Vector - destroy = event.RegisterEvent[event.NoPayload]() + destroy = event.RegisterEvent[struct{}]() sheet [][]*render.Sprite ) @@ -134,7 +134,7 @@ func main() { ray.DefaultCaster.CastDistance = floatgeom.Point2{x, y}.Sub(floatgeom.Point2{mx, my}).Magnitude() hits := ray.CastTo(floatgeom.Point2{x, y}, floatgeom.Point2{mx, my}) for _, hit := range hits { - event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, event.NoPayload{}) + event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, struct{}{}) } ctx.DrawForTime( render.NewLine(x, y, mx, my, color.RGBA{0, 128, 0, 128}), @@ -216,7 +216,7 @@ func NewEnemy(ctx *scene.Context) { return 0 }) - event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing event.NoPayload) event.Response { + event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing struct{}) event.Response { e.Destroy() return 0 }) diff --git a/examples/top-down-shooter-tutorial/6-performance/performance.go b/examples/top-down-shooter-tutorial/6-performance/performance.go index 3d2afd77..dfe1284e 100644 --- a/examples/top-down-shooter-tutorial/6-performance/performance.go +++ b/examples/top-down-shooter-tutorial/6-performance/performance.go @@ -35,7 +35,7 @@ var ( // the player's position vector playerPos physics.Vector - destroy = event.RegisterEvent[event.NoPayload]() + destroy = event.RegisterEvent[struct{}]() sheet [][]*render.Sprite ) @@ -143,7 +143,7 @@ func main() { ray.DefaultCaster.CastDistance = floatgeom.Point2{x, y}.Sub(floatgeom.Point2{mx, my}).Magnitude() hits := ray.CastTo(floatgeom.Point2{x, y}, floatgeom.Point2{mx, my}) for _, hit := range hits { - event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, event.NoPayload{}) + event.TriggerForCallerOn(ctx, hit.Zone.CID, destroy, struct{}{}) } ctx.DrawForTime( render.NewLine(x, y, mx, my, color.RGBA{0, 128, 0, 128}), @@ -236,7 +236,7 @@ func NewEnemy(ctx *scene.Context) { return 0 }) - event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing event.NoPayload) event.Response { + event.Bind(ctx, destroy, enemy, func(e *entities.Solid, nothing struct{}) event.Response { e.Destroy() return 0 }) From 8dd2b5958a954a0fda09e9e342c9e434ed2f51df Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 2 Apr 2022 12:06:07 -0500 Subject: [PATCH 40/41] event: add SetCID to Caller, stop returning CIDs from Register --- event/bind_test.go | 23 ++++++++++++----------- event/bus_test.go | 5 ++--- event/caller.go | 9 +++++++-- event/caller_test.go | 31 ++++++++++++++++++------------- event/trigger_test.go | 22 +++++++++++----------- 5 files changed, 50 insertions(+), 40 deletions(-) diff --git a/event/bind_test.go b/event/bind_test.go index 4fe1d942..073bf065 100644 --- a/event/bind_test.go +++ b/event/bind_test.go @@ -56,10 +56,10 @@ func TestBus_Unbind(t *testing.T) { func TestBind(t *testing.T) { t.Run("SuperficialCoverage", func(t *testing.T) { b := event.NewBus(event.NewCallerMap()) - var cid event.CallerID - id := b.GetCallerMap().Register(cid) + cid := randomCallerID() + b.GetCallerMap().Register(cid) var calls int32 - b1 := event.Bind(b, event.Enter, id, func(event.CallerID, event.EnterPayload) event.Response { + b1 := event.Bind(b, event.Enter, cid, func(*event.CallerID, event.EnterPayload) event.Response { atomic.AddInt32(&calls, 1) return 0 }) @@ -90,30 +90,31 @@ func TestGlobalBind(t *testing.T) { func TestBus_UnbindAllFrom(t *testing.T) { t.Run("Basic", func(t *testing.T) { b := event.NewBus(event.NewCallerMap()) - var cid event.CallerID - id := b.GetCallerMap().Register(cid) + var cid = new(event.CallerID) + b.GetCallerMap().Register(cid) var calls int32 for i := 0; i < 5; i++ { - b1 := event.Bind(b, event.Enter, id, func(event.CallerID, event.EnterPayload) event.Response { + b1 := event.Bind(b, event.Enter, cid, func(*event.CallerID, event.EnterPayload) event.Response { atomic.AddInt32(&calls, 1) return 0 }) <-b1.Bound } - id2 := b.GetCallerMap().Register(cid) - b1 := event.Bind(b, event.Enter, id2, func(event.CallerID, event.EnterPayload) event.Response { + oldID := *cid + b.GetCallerMap().Register(cid) + b1 := event.Bind(b, event.Enter, cid, func(*event.CallerID, event.EnterPayload) event.Response { atomic.AddInt32(&calls, 1) return 0 }) <-b1.Bound <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) if calls != 6 { - t.Fatal(expectedError("calls", 1, calls)) + t.Fatal(expectedError("calls", 6, calls)) } - <-b.UnbindAllFrom(id) + <-b.UnbindAllFrom(oldID) <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) if calls != 7 { - t.Fatal(expectedError("calls", 1, calls)) + t.Fatal(expectedError("calls", 7, calls)) } }) } diff --git a/event/bus_test.go b/event/bus_test.go index 8b227ddb..7993ba69 100644 --- a/event/bus_test.go +++ b/event/bus_test.go @@ -1,7 +1,6 @@ package event_test import ( - "math/rand" "sync/atomic" "testing" "time" @@ -28,11 +27,11 @@ func TestBus_SetCallerMap(t *testing.T) { t.Run("Basic", func(t *testing.T) { cm1 := event.NewCallerMap() b := event.NewBus(cm1) - c1 := event.CallerID(rand.Intn(10000)) + c1 := randomCallerID() b.GetCallerMap().Register(c1) cm2 := event.NewCallerMap() b.SetCallerMap(cm2) - if b.GetCallerMap().HasEntity(c1) { + if b.GetCallerMap().HasEntity(c1.CID()) { t.Fatal("event had old entity after changed caller map") } }) diff --git a/event/caller.go b/event/caller.go index 880fd1a7..1ab09bcd 100644 --- a/event/caller.go +++ b/event/caller.go @@ -12,6 +12,10 @@ func (c CallerID) CID() CallerID { return c } +func (c *CallerID) SetCID(c2 CallerID) { + *c = c2 +} + // Global is the CallerID associated with global bindings. A caller must not be assigned // this ID. Global may be used to manually create bindings scoped to no callers, but the GlobalBind function // should be preferred when possible for type safety. @@ -19,6 +23,7 @@ const Global CallerID = 0 type Caller interface { CID() CallerID + SetCID(CallerID) } // A CallerMap tracks CallerID mappings to Entities. @@ -41,7 +46,7 @@ func NewCallerMap() *CallerMap { // NextID finds the next available caller id // and returns it, after adding the given entity to // the caller map. -func (cm *CallerMap) Register(e Caller) CallerID { +func (cm *CallerMap) Register(e Caller) { cm.callersLock.Lock() defer cm.callersLock.Unlock() // Q: Why not use atomic? @@ -58,7 +63,7 @@ func (cm *CallerMap) Register(e Caller) CallerID { // Increment before assigning to preserve Global == caller 0 cm.highestID++ cm.callers[cm.highestID] = e - return cm.highestID + e.SetCID(cm.highestID) } // Get returns the entity corresponding to the given ID within diff --git a/event/caller_test.go b/event/caller_test.go index 10707660..6ca3b8e6 100644 --- a/event/caller_test.go +++ b/event/caller_test.go @@ -25,42 +25,47 @@ func TestNewCallerMap(t *testing.T) { }) } +func randomCallerID() *event.CallerID { + c1 := event.CallerID(rand.Intn(10000)) + return &c1 +} + func TestCallerMap_Register(t *testing.T) { t.Run("Basic", func(t *testing.T) { m := event.NewCallerMap() - c1 := event.CallerID(rand.Intn(10000)) - id := m.Register(c1) - c2 := m.GetEntity(id) + c1 := randomCallerID() + m.Register(c1) + c2 := m.GetEntity(c1.CID()) if c2 != c1 { t.Fatalf("unable to retrieve registered caller") } - if !m.HasEntity(id) { + if !m.HasEntity(c1.CID()) { t.Fatalf("caller map does not have registered caller") } }) t.Run("Remove", func(t *testing.T) { m := event.NewCallerMap() - c1 := event.CallerID(rand.Intn(10000)) - id := m.Register(c1) - m.RemoveEntity(id) - c3 := m.GetEntity(id) + c1 := randomCallerID() + m.Register(c1) + m.RemoveEntity(c1.CID()) + c3 := m.GetEntity(c1.CID()) if c3 != nil { t.Fatalf("get entity had registered caller after remove") } - if m.HasEntity(id) { + if m.HasEntity(c1.CID()) { t.Fatalf("caller map has registered caller after remove") } }) t.Run("Clear", func(t *testing.T) { m := event.NewCallerMap() - c1 := event.CallerID(rand.Intn(10000)) - id := m.Register(c1) + c1 := randomCallerID() + m.Register(c1) m.Clear() - c3 := m.GetEntity(id) + c3 := m.GetEntity(c1.CID()) if c3 != nil { t.Fatalf("get entity had registered caller after clear") } - if m.HasEntity(id) { + if m.HasEntity(c1.CID()) { t.Fatalf("caller map has registered caller after clear") } }) diff --git a/event/trigger_test.go b/event/trigger_test.go index 2dae1215..4b52c2ff 100644 --- a/event/trigger_test.go +++ b/event/trigger_test.go @@ -87,14 +87,14 @@ func TestBus_TriggerForCaller(t *testing.T) { }) t.Run("WithValidCallerID", func(t *testing.T) { b := event.NewBus(event.NewCallerMap()) - var cid event.CallerID - callerID := b.GetCallerMap().Register(cid) + var cid = new(event.CallerID) + b.GetCallerMap().Register(cid) id := event.UnsafeEventID(rand.Intn(100000)) errs := make(chan error) - binding := b.UnsafeBind(id, callerID, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + binding := b.UnsafeBind(id, *cid, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { defer close(errs) - if ci != callerID { - errs <- expectedError("callerID", callerID, ci) + if ci != *cid { + errs <- expectedError("callerID", *cid, ci) } if h != b { errs <- expectedError("bus", b, h) @@ -110,7 +110,7 @@ func TestBus_TriggerForCaller(t *testing.T) { t.Fatal("timeout waiting for bind to close channel") case <-binding.Bound: } - ch := b.TriggerForCaller(callerID, id, nil) + ch := b.TriggerForCaller(*cid, id, nil) select { case <-time.After(50 * time.Millisecond): t.Fatal("timeout waiting for trigger to close channel") @@ -195,14 +195,14 @@ func TestBus_Trigger(t *testing.T) { }) t.Run("WithValidCallerID", func(t *testing.T) { b := event.NewBus(event.NewCallerMap()) - var cid event.CallerID - callerID := b.GetCallerMap().Register(cid) + var cid = new(event.CallerID) + b.GetCallerMap().Register(cid) id := event.UnsafeEventID(rand.Intn(100000)) errs := make(chan error) - binding := b.UnsafeBind(id, event.CallerID(callerID), func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + binding := b.UnsafeBind(id, *cid, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { defer close(errs) - if ci != callerID { - errs <- expectedError("callerID", callerID, ci) + if ci != *cid { + errs <- expectedError("callerID", *cid, ci) } if h != b { errs <- expectedError("bus", b, h) From 419b01643c05b96670eeb9bfcc828d6075ebd1c1 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sat, 2 Apr 2022 13:10:41 -0500 Subject: [PATCH 41/41] Revert "event: add SetCID to Caller, stop returning CIDs from Register" This reverts commit 8dd2b5958a954a0fda09e9e342c9e434ed2f51df. --- event/bind_test.go | 23 +++++++++++------------ event/bus_test.go | 5 +++-- event/caller.go | 9 ++------- event/caller_test.go | 31 +++++++++++++------------------ event/trigger_test.go | 22 +++++++++++----------- 5 files changed, 40 insertions(+), 50 deletions(-) diff --git a/event/bind_test.go b/event/bind_test.go index 073bf065..4fe1d942 100644 --- a/event/bind_test.go +++ b/event/bind_test.go @@ -56,10 +56,10 @@ func TestBus_Unbind(t *testing.T) { func TestBind(t *testing.T) { t.Run("SuperficialCoverage", func(t *testing.T) { b := event.NewBus(event.NewCallerMap()) - cid := randomCallerID() - b.GetCallerMap().Register(cid) + var cid event.CallerID + id := b.GetCallerMap().Register(cid) var calls int32 - b1 := event.Bind(b, event.Enter, cid, func(*event.CallerID, event.EnterPayload) event.Response { + b1 := event.Bind(b, event.Enter, id, func(event.CallerID, event.EnterPayload) event.Response { atomic.AddInt32(&calls, 1) return 0 }) @@ -90,31 +90,30 @@ func TestGlobalBind(t *testing.T) { func TestBus_UnbindAllFrom(t *testing.T) { t.Run("Basic", func(t *testing.T) { b := event.NewBus(event.NewCallerMap()) - var cid = new(event.CallerID) - b.GetCallerMap().Register(cid) + var cid event.CallerID + id := b.GetCallerMap().Register(cid) var calls int32 for i := 0; i < 5; i++ { - b1 := event.Bind(b, event.Enter, cid, func(*event.CallerID, event.EnterPayload) event.Response { + b1 := event.Bind(b, event.Enter, id, func(event.CallerID, event.EnterPayload) event.Response { atomic.AddInt32(&calls, 1) return 0 }) <-b1.Bound } - oldID := *cid - b.GetCallerMap().Register(cid) - b1 := event.Bind(b, event.Enter, cid, func(*event.CallerID, event.EnterPayload) event.Response { + id2 := b.GetCallerMap().Register(cid) + b1 := event.Bind(b, event.Enter, id2, func(event.CallerID, event.EnterPayload) event.Response { atomic.AddInt32(&calls, 1) return 0 }) <-b1.Bound <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) if calls != 6 { - t.Fatal(expectedError("calls", 6, calls)) + t.Fatal(expectedError("calls", 1, calls)) } - <-b.UnbindAllFrom(oldID) + <-b.UnbindAllFrom(id) <-event.TriggerOn(b, event.Enter, event.EnterPayload{}) if calls != 7 { - t.Fatal(expectedError("calls", 7, calls)) + t.Fatal(expectedError("calls", 1, calls)) } }) } diff --git a/event/bus_test.go b/event/bus_test.go index 7993ba69..8b227ddb 100644 --- a/event/bus_test.go +++ b/event/bus_test.go @@ -1,6 +1,7 @@ package event_test import ( + "math/rand" "sync/atomic" "testing" "time" @@ -27,11 +28,11 @@ func TestBus_SetCallerMap(t *testing.T) { t.Run("Basic", func(t *testing.T) { cm1 := event.NewCallerMap() b := event.NewBus(cm1) - c1 := randomCallerID() + c1 := event.CallerID(rand.Intn(10000)) b.GetCallerMap().Register(c1) cm2 := event.NewCallerMap() b.SetCallerMap(cm2) - if b.GetCallerMap().HasEntity(c1.CID()) { + if b.GetCallerMap().HasEntity(c1) { t.Fatal("event had old entity after changed caller map") } }) diff --git a/event/caller.go b/event/caller.go index 1ab09bcd..880fd1a7 100644 --- a/event/caller.go +++ b/event/caller.go @@ -12,10 +12,6 @@ func (c CallerID) CID() CallerID { return c } -func (c *CallerID) SetCID(c2 CallerID) { - *c = c2 -} - // Global is the CallerID associated with global bindings. A caller must not be assigned // this ID. Global may be used to manually create bindings scoped to no callers, but the GlobalBind function // should be preferred when possible for type safety. @@ -23,7 +19,6 @@ const Global CallerID = 0 type Caller interface { CID() CallerID - SetCID(CallerID) } // A CallerMap tracks CallerID mappings to Entities. @@ -46,7 +41,7 @@ func NewCallerMap() *CallerMap { // NextID finds the next available caller id // and returns it, after adding the given entity to // the caller map. -func (cm *CallerMap) Register(e Caller) { +func (cm *CallerMap) Register(e Caller) CallerID { cm.callersLock.Lock() defer cm.callersLock.Unlock() // Q: Why not use atomic? @@ -63,7 +58,7 @@ func (cm *CallerMap) Register(e Caller) { // Increment before assigning to preserve Global == caller 0 cm.highestID++ cm.callers[cm.highestID] = e - e.SetCID(cm.highestID) + return cm.highestID } // Get returns the entity corresponding to the given ID within diff --git a/event/caller_test.go b/event/caller_test.go index 6ca3b8e6..10707660 100644 --- a/event/caller_test.go +++ b/event/caller_test.go @@ -25,47 +25,42 @@ func TestNewCallerMap(t *testing.T) { }) } -func randomCallerID() *event.CallerID { - c1 := event.CallerID(rand.Intn(10000)) - return &c1 -} - func TestCallerMap_Register(t *testing.T) { t.Run("Basic", func(t *testing.T) { m := event.NewCallerMap() - c1 := randomCallerID() - m.Register(c1) - c2 := m.GetEntity(c1.CID()) + c1 := event.CallerID(rand.Intn(10000)) + id := m.Register(c1) + c2 := m.GetEntity(id) if c2 != c1 { t.Fatalf("unable to retrieve registered caller") } - if !m.HasEntity(c1.CID()) { + if !m.HasEntity(id) { t.Fatalf("caller map does not have registered caller") } }) t.Run("Remove", func(t *testing.T) { m := event.NewCallerMap() - c1 := randomCallerID() - m.Register(c1) - m.RemoveEntity(c1.CID()) - c3 := m.GetEntity(c1.CID()) + c1 := event.CallerID(rand.Intn(10000)) + id := m.Register(c1) + m.RemoveEntity(id) + c3 := m.GetEntity(id) if c3 != nil { t.Fatalf("get entity had registered caller after remove") } - if m.HasEntity(c1.CID()) { + if m.HasEntity(id) { t.Fatalf("caller map has registered caller after remove") } }) t.Run("Clear", func(t *testing.T) { m := event.NewCallerMap() - c1 := randomCallerID() - m.Register(c1) + c1 := event.CallerID(rand.Intn(10000)) + id := m.Register(c1) m.Clear() - c3 := m.GetEntity(c1.CID()) + c3 := m.GetEntity(id) if c3 != nil { t.Fatalf("get entity had registered caller after clear") } - if m.HasEntity(c1.CID()) { + if m.HasEntity(id) { t.Fatalf("caller map has registered caller after clear") } }) diff --git a/event/trigger_test.go b/event/trigger_test.go index 4b52c2ff..2dae1215 100644 --- a/event/trigger_test.go +++ b/event/trigger_test.go @@ -87,14 +87,14 @@ func TestBus_TriggerForCaller(t *testing.T) { }) t.Run("WithValidCallerID", func(t *testing.T) { b := event.NewBus(event.NewCallerMap()) - var cid = new(event.CallerID) - b.GetCallerMap().Register(cid) + var cid event.CallerID + callerID := b.GetCallerMap().Register(cid) id := event.UnsafeEventID(rand.Intn(100000)) errs := make(chan error) - binding := b.UnsafeBind(id, *cid, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + binding := b.UnsafeBind(id, callerID, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { defer close(errs) - if ci != *cid { - errs <- expectedError("callerID", *cid, ci) + if ci != callerID { + errs <- expectedError("callerID", callerID, ci) } if h != b { errs <- expectedError("bus", b, h) @@ -110,7 +110,7 @@ func TestBus_TriggerForCaller(t *testing.T) { t.Fatal("timeout waiting for bind to close channel") case <-binding.Bound: } - ch := b.TriggerForCaller(*cid, id, nil) + ch := b.TriggerForCaller(callerID, id, nil) select { case <-time.After(50 * time.Millisecond): t.Fatal("timeout waiting for trigger to close channel") @@ -195,14 +195,14 @@ func TestBus_Trigger(t *testing.T) { }) t.Run("WithValidCallerID", func(t *testing.T) { b := event.NewBus(event.NewCallerMap()) - var cid = new(event.CallerID) - b.GetCallerMap().Register(cid) + var cid event.CallerID + callerID := b.GetCallerMap().Register(cid) id := event.UnsafeEventID(rand.Intn(100000)) errs := make(chan error) - binding := b.UnsafeBind(id, *cid, func(ci event.CallerID, h event.Handler, i interface{}) event.Response { + binding := b.UnsafeBind(id, event.CallerID(callerID), func(ci event.CallerID, h event.Handler, i interface{}) event.Response { defer close(errs) - if ci != *cid { - errs <- expectedError("callerID", *cid, ci) + if ci != callerID { + errs <- expectedError("callerID", callerID, ci) } if h != b { errs <- expectedError("bus", b, h)