Created
September 29, 2017 17:39
-
-
Save vassvik/1fe18aafe111f9448d484f3605666871 to your computer and use it in GitHub Desktop.
An event queue backed by a ring buffer and tagged unions implemented in Odin. (Note: File names are .go for syntax highlighting)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "core:fmt.odin"; | |
// Define event types, based on GLFW's events | |
Window_Iconify_Event :: struct { iconified: i32}; | |
Window_Refresh_Event :: struct { }; | |
Window_Focus_Event :: struct { focused: i32 }; | |
Window_Close_Event :: struct { }; | |
Window_Size_Event :: struct { width, height: i32 }; | |
Window_Pos_Event :: struct { xpos, ypos: i32 }; | |
Framebuffer_Size_Event :: struct { width, height: i32 } | |
Drop_Event :: struct { count: i32, paths: ^^u8 }; | |
Monitor_Event :: struct { }; | |
Key_Event :: struct { key, scancode, action, mods: i32 }; | |
Mouse_Cursor_Event :: struct { xpos, ypos: f64 }; | |
Mouse_Button_Event :: struct { button, action, mods: i32 }; | |
Mouse_Scroll_event :: struct { xoffset, yoffset: f64 }; | |
Text_Event :: struct { codepoint: u32 }; | |
Text_Mods_Event :: struct { codepoint: u32, mods: i32 }; | |
Cursor_Enter_Event :: struct { entered: i32 }; | |
Joystick_Event :: struct { joy, event: i32 }; | |
// Define event tagged union | |
Event :: union { | |
Window_Pos_Event, | |
Window_Size_Event, | |
Window_Close_Event, | |
Window_Refresh_Event, | |
Window_Focus_Event, | |
Window_Iconify_Event, | |
Monitor_Event, | |
Framebuffer_Size_Event, | |
Drop_Event, | |
Key_Event, | |
Mouse_Cursor_Event, | |
Mouse_Button_Event, | |
Mouse_Scroll_event, | |
Text_Event, | |
Text_Mods_Event, | |
Cursor_Enter_Event, | |
Joystick_Event, | |
}; | |
// Define an event queue type using a ring buffer as backing | |
// | |
// Note: This queue is designed to only be increased in size, | |
// since if the number of events has reached a certain size | |
// it is likely to reach that size again | |
// | |
// Note: This could readily be made generic using parametric types | |
Event_Queue :: struct { | |
values: []Event, | |
start: int, | |
stop: int, | |
length: int, | |
capacity: int, | |
}; | |
// Functions: | |
// print: prints the queue | |
// new: creates a new, empty queue (with capacity 8) | |
// free: frees the memory used to store the events | |
// clear: resets the queue, but keeps the data in memory | |
// enqueue: add an event to the queue | |
// unqueue: remove an event from the queue, returning it | |
// peek: look at the first event | |
queue_print :: proc(using eq: ^Event_Queue) { | |
for i in 0..length { | |
fmt.printf("%d: %d/%d, %v\n", i, (start+i)%capacity, capacity-1, values[(start+i)%capacity]); | |
} | |
} | |
queue_new :: proc() -> Event_Queue { | |
return Event_Queue{make([]Event, 8), 0, 0, 0, 8}; | |
} | |
queue_free :: proc(using eq: ^Event_Queue) { | |
queue_clear(eq); | |
free(values); | |
} | |
queue_clear :: proc(using eq: ^Event_Queue) { | |
start = 0; | |
stop = 0; | |
length = 0; | |
} | |
queue_enqueue :: proc(using eq: ^Event_Queue, e: Event) { | |
if length == capacity { | |
new_values := make([]Event, 2*capacity); | |
defer { | |
free(values); | |
values = new_values; | |
} | |
// @NOTE: capacity will always be a power of two, so can replace modulus by bitwise AND for performance | |
for i in 0..length do new_values[i] = values[(start+i)%capacity]; | |
new_values[length] = e; | |
length += 1; | |
start = 0; | |
stop = length; | |
capacity = 2*capacity; | |
} else { | |
values[stop] = e; | |
stop = (stop + 1)% capacity; | |
length += 1; | |
} | |
} | |
queue_unqueue :: proc(using eq: ^Event_Queue) -> (e: Event) { | |
if length == 0 do return Event{}; | |
defer start = (start + 1) % capacity; | |
length -= 1; | |
return values[start]; | |
} | |
queue_peek :: proc(using eq: ^Event_Queue) -> (e: Event) { | |
if length == 0 do return Event{}; | |
return values[start]; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "core:fmt.odin"; | |
using import "events.odin"; | |
main :: proc() { | |
// create a new, empty (8 capacity) queue | |
eq := queue_new(); | |
defer queue_free(&eq); | |
queue_print(&eq); | |
fmt.println(); | |
// add 8 Key events, should fill up the queue, and be in order, start from index 0 | |
for i in 0..8 { | |
queue_enqueue(&eq, Key_Event{cast(i32)i, 0, 0, 0}); | |
} | |
queue_print(&eq); | |
fmt.println(); | |
// remove 3 events from the queue, there should now be 5 Key events in the queue, | |
// starting at index 3. the first 3 events are now undefined | |
for i in 0..3 { | |
fmt.println("removed", queue_unqueue(&eq)); | |
} | |
queue_print(&eq); | |
fmt.println(); | |
// add 3 more events, the queue will be filled up yet again, totalling 8 elements, | |
// starting at index 3 and wrapping around, i.e. the "first" 3 events are now Mouse Button events | |
// and the rest are Key events | |
for i in 0..3 { | |
queue_enqueue(&eq, Mouse_Button_Event{cast(i32)i, 0, 0}); | |
} | |
queue_print(&eq); | |
fmt.println(); | |
// add 4 more Mouse Cursor events, there should now be 12 events total. | |
// the storage container has been expanded, and the events have been rearranged to start at index 0 | |
// there should now be 5 Key events, 3 Mouse Button events and 4 Mouse Cursor events. | |
for i in 0..4 { | |
queue_enqueue(&eq, Mouse_Cursor_Event{cast(f64)i, 0.0}); | |
} | |
queue_print(&eq); | |
fmt.println(); | |
// remove 3 event from the queue, there should now be 9 events in the queue, | |
// starting at index 3. | |
for i in 0..3 { | |
fmt.println("removed", queue_unqueue(&eq)); | |
} | |
queue_print(&eq); | |
fmt.println(); | |
// Finally add 7 text events, the queue should be full again, but starting at index 3, wrapping around | |
// Should be 2 Key events, followed by 3 Mouse Button events, followed by 4 Mouse Cursor events, | |
// followed by 7 Text events (the last 3 wrapping around) | |
for i in 0..7 { | |
queue_enqueue(&eq, Text_Event{cast(u32)i}); | |
} | |
queue_print(&eq); | |
fmt.println(); | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "core:fmt.odin"; | |
import "core:strings.odin"; | |
using import "events.odin"; | |
import "shared:odin-glfw/glfw.odin"; | |
import "shared:odin-gl/gl.odin"; | |
import "shared:odin-gl_font/font.odin"; | |
// global event queue | |
eq := queue_new(); | |
main :: proc() { | |
// | |
error_callback :: proc(error: i32, desc: ^u8) #cc_c { | |
fmt.printf("Error code %d:\n %s\n", error, strings.to_odin_string(desc)); | |
} | |
glfw.SetErrorCallback(error_callback); | |
// | |
if glfw.Init() == 0 do return; | |
defer glfw.Terminate(); | |
// | |
glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 4); | |
glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 5); | |
glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE); | |
resx, resy : i32 = 1600, 900; | |
window := glfw.CreateWindow(resx, resy, "test queue", nil, nil); | |
if window == nil do return; | |
// | |
glfw.MakeContextCurrent(window); | |
glfw.SwapInterval(1); | |
// | |
glfw.SetKeyCallback(window, proc(window: ^glfw.window, key, scancode, action, mods: i32) #cc_c { queue_enqueue(&eq, Key_Event{ key, scancode, action, mods})}); | |
glfw.SetCursorPosCallback(window, proc(window: ^glfw.window, xpos, ypos: f64) #cc_c { queue_enqueue(&eq, Mouse_Cursor_Event{ xpos, ypos})}); | |
glfw.SetMouseButtonCallback(window, proc(window: ^glfw.window, button, action, mods: i32) #cc_c { queue_enqueue(&eq, Mouse_Button_Event{ button, action, mods})}); | |
glfw.SetScrollCallback(window, proc(window: ^glfw.window, xoffset, yoffset: f64) #cc_c { queue_enqueue(&eq, Mouse_Scroll_event{ xoffset, yoffset})}); | |
glfw.SetCharCallback(window, proc(window: ^glfw.window, codepoint: u32) #cc_c { queue_enqueue(&eq, Text_Event { codepoint})}); | |
glfw.SetCharModsCallback(window, proc(window: ^glfw.window, codepoint: u32, mods: i32) #cc_c { queue_enqueue(&eq, Text_Mods_Event{codepoint, mods})}); | |
glfw.SetCursorEnterCallback(window, proc(window: ^glfw.window, entered: i32) #cc_c { queue_enqueue(&eq, Cursor_Enter_Event{ entered})}); | |
glfw.SetJoystickCallback(window, proc(joy, event: i32) #cc_c { queue_enqueue(&eq, Joystick_Event{ joy, event})}); | |
glfw.SetWindowIconifyCallback(window, proc(window: ^glfw.window, iconified: i32) #cc_c { queue_enqueue(&eq, Window_Iconify_Event{ iconified})}); | |
glfw.SetWindowRefreshCallback(window, proc(window: ^glfw.window) #cc_c { queue_enqueue(&eq, Window_Refresh_Event{ })}); | |
glfw.SetWindowFocusCallback(window, proc(window: ^glfw.window, focused: i32) #cc_c { queue_enqueue(&eq, Window_Focus_Event{ focused })}); | |
glfw.SetWindowCloseCallback(window, proc(window: ^glfw.window) #cc_c { queue_enqueue(&eq, Window_Close_Event{ })}); | |
glfw.SetWindowSizeCallback(window, proc(window: ^glfw.window, width, height: i32) #cc_c { queue_enqueue(&eq, Window_Size_Event{ width, height })}); | |
glfw.SetWindowPosCallback(window, proc(window: ^glfw.window, xpos, ypos: i32) #cc_c { queue_enqueue(&eq, Window_Pos_Event{ xpos, ypos })}); | |
glfw.SetFramebufferSizeCallback(window, proc(window: ^glfw.window, width, height: i32) #cc_c { queue_enqueue(&eq, Framebuffer_Size_Event{ width, height })}); | |
glfw.SetDropCallback(window, proc(window: ^glfw.window, count: i32, paths: ^^u8) #cc_c { queue_enqueue(&eq, Drop_Event{ count, paths })}); | |
glfw.SetMonitorCallback(window, proc(window: ^glfw.window) #cc_c { queue_enqueue(&eq, Monitor_Event{ })}); | |
// | |
set_proc_address :: proc(p: rawptr, name: string) { | |
(cast(^rawptr)p)^ = rawptr(glfw.GetProcAddress(&name[0])); | |
} | |
gl.load_up_to(4, 5, set_proc_address); | |
// | |
if !font.init("extra/font_3x1.bin", "shaders/shader_font.vs", "shaders/shader_font.fs", set_proc_address) do return; | |
defer font.cleanup(); | |
// | |
gl.ClearColor(1.0, 1.0, 1.0, 1.0); | |
// | |
t1 := glfw.GetTime(); | |
for glfw.WindowShouldClose(window) == glfw.FALSE && glfw.GetKey(window, glfw.KEY_ESCAPE) == 0 { | |
// | |
t2 := glfw.GetTime(); | |
t1 = t2; | |
glfw.calculate_frame_timings(window); | |
// | |
glfw.PollEvents(); | |
// | |
gl.Clear(gl.COLOR_BUFFER_BIT); | |
// | |
y_pos : f32 = 0.0; | |
font.draw_format(0.0, y_pos, 32.0, 1, "frame time = %.3f", (t2 - t1)*1000.0); y_pos += 32.0; // formatted string with explicit palette index passing | |
// | |
using eq; | |
for { | |
e := queue_unqueue(&eq); | |
if e == nil do break; | |
font.draw_format(0.0, y_pos, 32.0, 0, "%v\n", e); y_pos += 32.0; | |
} | |
glfw.SwapBuffers(window); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment