event_pool.go RawHistoryFolder
1//go:build linux
2// +build linux
3
4package wlturbo
5
6import (
7 "sync"
8 "sync/atomic"
9)
10
11// Event pool for zero-allocation event handling
12var eventPool = sync.Pool{
13 New: func() interface{} {
14 return &Event{
15 data: make([]byte, 0, 4096), // Pre-allocate 4KB
16 }
17 },
18}
19
20// EventHandler is a function type for handling events with zero allocations
21type EventHandler func(event *Event)
22
23// EventDispatcher handles high-performance event dispatching
24type EventDispatcher struct {
25 // Lock-free handler lookup using atomic operations
26 handlers [1024]atomic.Pointer[handlerEntry] // Fixed-size array for object IDs 0-1023
27
28 // Extended handlers for object IDs >= 1024 (rare case)
29 extHandlers sync.Map
30
31 // Pre-allocated event objects to avoid allocations
32 eventCache [64]Event
33 eventIndex atomic.Uint32
34}
35
36// handlerEntry stores handlers for a specific object
37type handlerEntry struct {
38 // Array of handlers indexed by opcode (most objects have < 16 opcodes)
39 handlers [32]EventHandler
40
41 // Extended handlers for opcodes >= 32 (very rare)
42 extHandlers *sync.Map
43}
44
45// NewEventDispatcher creates a high-performance event dispatcher
46func NewEventDispatcher() *EventDispatcher {
47 return &EventDispatcher{}
48}
49
50// RegisterHandler registers an event handler with zero allocation in common case
51func (d *EventDispatcher) RegisterHandler(objectID uint32, opcode uint16, handler EventHandler) {
52 if objectID < 1024 {
53 // Fast path: use lock-free array
54 for {
55 entry := d.handlers[objectID].Load()
56 if entry == nil {
57 // Create new entry
58 newEntry := &handlerEntry{}
59 if opcode < 32 {
60 newEntry.handlers[opcode] = handler
61 } else {
62 newEntry.extHandlers = &sync.Map{}
63 newEntry.extHandlers.Store(opcode, handler)
64 }
65
66 // Try to set atomically
67 if d.handlers[objectID].CompareAndSwap(nil, newEntry) {
68 return
69 }
70 // Retry if someone else created it
71 continue
72 }
73
74 // Entry exists, update it
75 if opcode < 32 {
76 // Direct array access (no locking needed for write)
77 entry.handlers[opcode] = handler
78 } else {
79 // Extended handlers
80 if entry.extHandlers == nil {
81 entry.extHandlers = &sync.Map{}
82 }
83 entry.extHandlers.Store(opcode, handler)
84 }
85 return
86 }
87 } else {
88 // Slow path: use sync.Map for large object IDs
89 entry, _ := d.extHandlers.LoadOrStore(objectID, &handlerEntry{})
90 h := entry.(*handlerEntry)
91
92 if opcode < 32 {
93 h.handlers[opcode] = handler
94 } else {
95 if h.extHandlers == nil {
96 h.extHandlers = &sync.Map{}
97 }
98 h.extHandlers.Store(opcode, handler)
99 }
100 }
101}
102
103// Dispatch dispatches an event with minimal overhead
104//
105//go:inline
106func (d *EventDispatcher) Dispatch(objectID uint32, opcode uint16, data []byte) {
107 var handler EventHandler
108
109 // Fast lookup path
110 if objectID < 1024 {
111 entry := d.handlers[objectID].Load()
112 if entry != nil {
113 if opcode < 32 {
114 handler = entry.handlers[opcode]
115 } else if entry.extHandlers != nil {
116 if h, ok := entry.extHandlers.Load(opcode); ok {
117 handler = h.(EventHandler)
118 }
119 }
120 }
121 } else {
122 // Slow path for large object IDs
123 if entry, ok := d.extHandlers.Load(objectID); ok {
124 h := entry.(*handlerEntry)
125 if opcode < 32 {
126 handler = h.handlers[opcode]
127 } else if h.extHandlers != nil {
128 if fn, ok := h.extHandlers.Load(opcode); ok {
129 handler = fn.(EventHandler)
130 }
131 }
132 }
133 }
134
135 if handler == nil {
136 return
137 }
138
139 // Get event from pool
140 event := eventPool.Get().(*Event)
141 event.ProxyID = objectID
142 event.Opcode = opcode
143 event.data = append(event.data[:0], data...) // Reuse backing array
144 event.offset = 0
145
146 // Call handler
147 handler(event)
148
149 // Return to pool
150 eventPool.Put(event)
151}
152
153// BatchDispatch processes multiple events in a batch for better cache locality
154func (d *EventDispatcher) BatchDispatch(events []RawEvent) {
155 // Process events in batches to improve cache performance
156 for i := range events {
157 e := &events[i]
158 d.Dispatch(e.ObjectID, e.Opcode, e.Data)
159 }
160}
161
162// RawEvent represents a raw event before dispatch
163type RawEvent struct {
164 ObjectID uint32
165 Opcode uint16
166 Data []byte
167}
168
169// DirectDispatcher provides the absolute fastest dispatch path for hot events
170type DirectDispatcher struct {
171 // Direct function pointers for the hottest paths (e.g., pointer motion)
172 pointerMotion func(surfaceX, surfaceY Fixed)
173 pointerButton func(button, state uint32)
174 keyboardKey func(key, state uint32)
175 frameCallback func(callbackData uint32)
176
177 // Fallback to regular dispatcher
178 fallback *EventDispatcher
179}
180
181// DispatchPointerMotion dispatches pointer motion events with zero overhead
182//
183//go:inline
184func (d *DirectDispatcher) DispatchPointerMotion(surfaceX, surfaceY Fixed) {
185 if d.pointerMotion != nil {
186 d.pointerMotion(surfaceX, surfaceY)
187 }
188}
189
190// DispatchPointerButton dispatches pointer button events with zero overhead
191//
192//go:inline
193func (d *DirectDispatcher) DispatchPointerButton(button, state uint32) {
194 if d.pointerButton != nil {
195 d.pointerButton(button, state)
196 }
197}
198
199// Ensure cache line alignment for hot data structures
200type cacheLinePad [64]byte
201
202// PerformanceStats tracks dispatcher performance
203type PerformanceStats struct {
204 EventsDispatched atomic.Uint64
205 _ cacheLinePad
206 NanosecondsSpent atomic.Uint64
207 _ cacheLinePad
208 CacheMisses atomic.Uint64
209 _ cacheLinePad
210}
211
212// GetAverageLatency returns average dispatch latency in nanoseconds
213func (s *PerformanceStats) GetAverageLatency() uint64 {
214 events := s.EventsDispatched.Load()
215 if events == 0 {
216 return 0
217 }
218 return s.NanosecondsSpent.Load() / events
219}