Skip to content

Instantly share code, notes, and snippets.

@vassvik
Created September 29, 2017 17:39
Show Gist options
  • Save vassvik/1fe18aafe111f9448d484f3605666871 to your computer and use it in GitHub Desktop.
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)
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];
}
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();
}
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