v0.1.0 4a0dffe

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close