Refactor modules architecture
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
package base
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
// -----------------------------------------
|
||||
// Types
|
||||
|
||||
// event category, bitfield (?)
|
||||
|
||||
// Event_Data :: union {
|
||||
Event :: union {
|
||||
Window_Close_Event,
|
||||
Window_Resize_Event,
|
||||
Joystick_Connect_Event,
|
||||
Joystick_Disconnect_Event,
|
||||
Key_Press_Event,
|
||||
Key_Release_Event,
|
||||
Key_Repeat_Event,
|
||||
Mouse_Button_Press_Event,
|
||||
Mouse_Button_Release_Event,
|
||||
Mouse_Position_Event,
|
||||
Mouse_Scroll_Event,
|
||||
}
|
||||
|
||||
// Event :: struct {
|
||||
// data: Event_Data,
|
||||
// handled: bool,
|
||||
// }
|
||||
|
||||
Window_Close_Event :: struct {
|
||||
handled: bool,
|
||||
}
|
||||
|
||||
Window_Resize_Event :: struct {
|
||||
handled: bool,
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
Joystick_Connect_Event :: struct {
|
||||
handled: bool,
|
||||
id: i32,
|
||||
}
|
||||
|
||||
Joystick_Disconnect_Event :: struct {
|
||||
handled: bool,
|
||||
id: i32,
|
||||
}
|
||||
|
||||
Key_Press_Event :: struct {
|
||||
handled: bool,
|
||||
key: Key,
|
||||
mods: Mod_Set,
|
||||
}
|
||||
|
||||
Key_Release_Event :: struct {
|
||||
handled: bool,
|
||||
key: Key,
|
||||
mods: Mod_Set,
|
||||
}
|
||||
|
||||
Key_Repeat_Event :: struct {
|
||||
handled: bool,
|
||||
key: Key,
|
||||
mods: Mod_Set,
|
||||
}
|
||||
|
||||
Mouse_Button_Press_Event :: struct {
|
||||
handled: bool,
|
||||
button: Mouse_Button,
|
||||
mods: Mod_Set,
|
||||
}
|
||||
|
||||
Mouse_Button_Release_Event :: struct {
|
||||
handled: bool,
|
||||
button: Mouse_Button,
|
||||
mods: Mod_Set,
|
||||
}
|
||||
|
||||
Mouse_Position_Event :: struct {
|
||||
handled: bool,
|
||||
x_pos: f32,
|
||||
y_pos: f32,
|
||||
}
|
||||
|
||||
Mouse_Scroll_Event :: struct {
|
||||
handled: bool,
|
||||
x_offset: f32,
|
||||
y_offset: f32,
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Variables
|
||||
|
||||
// I want to have a list of Listeners per Event type, but this is state
|
||||
// listeners: map[typeid][dynamic]Listener
|
||||
|
||||
// -----------------------------------------
|
||||
// Public functions
|
||||
|
||||
// Dispatcher(Event) -> forwards to the right function
|
||||
on_event :: proc(event: Event) {
|
||||
fmt.println("toby!")
|
||||
|
||||
#partial switch e in event {
|
||||
case Window_Close_Event:
|
||||
fmt.println("Close")
|
||||
case Window_Resize_Event:
|
||||
fmt.println("Resize:", e.width, e.height)
|
||||
|
||||
case Key_Press_Event:
|
||||
fmt.println("Key press", e.key, e.mods)
|
||||
case Key_Release_Event:
|
||||
fmt.println("Key release", e.key, e.mods)
|
||||
case Key_Repeat_Event:
|
||||
fmt.println("Key repeat", e.key, e.mods)
|
||||
|
||||
case Mouse_Button_Press_Event:
|
||||
fmt.println("Mouse press", e.button, e.mods)
|
||||
case Mouse_Button_Release_Event:
|
||||
fmt.println("Mouse release", e.button, e.mods)
|
||||
case Mouse_Position_Event:
|
||||
fmt.printfln("Mouse pos: {}x{}", e.x_pos, e.y_pos)
|
||||
|
||||
case Joystick_Connect_Event:
|
||||
fmt.println("Joy con:", e.id)
|
||||
case Joystick_Disconnect_Event:
|
||||
fmt.println("Joy dis:", e.id)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Notes:
|
||||
|
||||
Have the events queued up into a frame buffer that gets drained at a specific time.
|
||||
*/
|
||||
@@ -0,0 +1,231 @@
|
||||
package base
|
||||
|
||||
import "core:c"
|
||||
import "core:dynlib"
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import "core:time"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
LIB_EXT :: ".dll"
|
||||
} else when ODIN_OS == .Darwin {
|
||||
LIB_EXT :: ".dylib"
|
||||
} else {
|
||||
LIB_EXT :: ".so"
|
||||
}
|
||||
|
||||
GAME_LIB_DIR :: "build/"
|
||||
GAME_LIB_PATH :: GAME_LIB_DIR + "game" + LIB_EXT
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Hot_Reload :: struct {
|
||||
loaded_libs: [dynamic]Game_API,
|
||||
}
|
||||
|
||||
Hot_Reload_Error :: enum u8 {
|
||||
None = 0,
|
||||
Load_Game_Lib_Failed = 1,
|
||||
Unimplemented = 127,
|
||||
Okay = None,
|
||||
}
|
||||
|
||||
Error :: union #shared_nil {
|
||||
Hot_Reload_Error,
|
||||
}
|
||||
|
||||
Game_API :: struct {
|
||||
lib: dynlib.Library,
|
||||
memory: proc() -> rawptr,
|
||||
memory_free: proc(),
|
||||
memory_size: proc() -> int,
|
||||
memory_set: proc(mem: rawptr),
|
||||
settings: proc() -> Settings,
|
||||
init_once: proc(),
|
||||
init: proc(),
|
||||
update: proc(_: f32),
|
||||
render: proc(),
|
||||
destroy: proc(),
|
||||
should_close: proc() -> bool,
|
||||
force_reload: proc() -> bool,
|
||||
force_restart: proc() -> bool,
|
||||
modification_time: time.Time,
|
||||
api_version: int, // iteration of the game lib
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
hot_reload_init :: proc() -> (hr: Hot_Reload, error: Error) {
|
||||
api: Game_API
|
||||
|
||||
api_ok := load_game_lib(&api)
|
||||
if !api_ok {
|
||||
fmt.println("error: failed to load Game API")
|
||||
return {}, .Load_Game_Lib_Failed
|
||||
}
|
||||
|
||||
hr.loaded_libs = make([dynamic]Game_API, 0, 0, context.allocator)
|
||||
append(&hr.loaded_libs, api)
|
||||
|
||||
return hr, nil
|
||||
}
|
||||
|
||||
hot_reload_destroy :: proc(state: ^Hot_Reload) {
|
||||
for &api in state.loaded_libs {
|
||||
unload_game_lib(&api)
|
||||
}
|
||||
|
||||
delete(state.loaded_libs)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
active_lib :: proc(hr: ^Hot_Reload) -> ^Game_API {
|
||||
return &hr.loaded_libs[len(hr.loaded_libs) - 1]
|
||||
}
|
||||
|
||||
should_close :: proc(hr: ^Hot_Reload) -> bool {
|
||||
api := active_lib(hr)
|
||||
if api.lib == nil do return true
|
||||
return api.should_close()
|
||||
}
|
||||
|
||||
copy_lib :: proc(to: string) -> bool {
|
||||
copy_err := os.copy_file(to, GAME_LIB_PATH)
|
||||
if copy_err != nil {
|
||||
fmt.printfln(
|
||||
"error: failed to copy {0} to {1}: {2:v}",
|
||||
GAME_LIB_PATH,
|
||||
to,
|
||||
copy_err,
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Load game lib symbols
|
||||
load_game_lib :: proc(api: ^Game_API) -> bool {
|
||||
mod_time, mod_time_err := os.last_write_time_by_name(GAME_LIB_PATH)
|
||||
if mod_time_err != os.ERROR_NONE {
|
||||
fmt.printfln(
|
||||
"error: failed getting last write time of {0}, error code: {1}",
|
||||
GAME_LIB_PATH,
|
||||
mod_time_err,
|
||||
)
|
||||
return false
|
||||
}
|
||||
api.modification_time = mod_time
|
||||
|
||||
lib_name := fmt.tprintf(
|
||||
GAME_LIB_DIR + "game_{0}" + LIB_EXT,
|
||||
api.api_version,
|
||||
)
|
||||
copy_lib(lib_name) or_return
|
||||
|
||||
// Match the names of the fields in Game_API to symbols in the game DLL
|
||||
_, ok := dynlib.initialize_symbols(api, lib_name, "", "lib")
|
||||
if !ok {
|
||||
fmt.printfln(
|
||||
"error: failed initializing symbols: {0}",
|
||||
dynlib.last_error(),
|
||||
)
|
||||
} else {
|
||||
// Odin's shared libraries don't register a constructor, so the lib's
|
||||
// runtime init (@init procs like os.stdout setup) never runs on load.
|
||||
// Call it explicitly.
|
||||
// Windows doesn't need this: its dynamic build exports DllMain, which
|
||||
// the OS calls on load and which runs _startup_runtime itself.
|
||||
when ODIN_OS != .Windows {
|
||||
Entry :: #type proc "c" () -> c.int
|
||||
entry_ptr, entry_ok := dynlib.symbol_address(
|
||||
api.lib,
|
||||
"_odin_entry_point",
|
||||
)
|
||||
if entry_ok do (transmute(Entry)entry_ptr)()
|
||||
}
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// Unload game lib, used during full restarts
|
||||
unload_game_lib :: proc(api: ^Game_API) {
|
||||
if api.lib != nil {
|
||||
if !dynlib.unload_library(api.lib) {
|
||||
fmt.eprintfln(
|
||||
"error: failed unloading lib: {0}",
|
||||
dynlib.last_error(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if os.remove(
|
||||
fmt.tprintf(GAME_LIB_DIR + "game_{0}" + LIB_EXT, api.api_version),
|
||||
) !=
|
||||
nil {
|
||||
fmt.printfln(
|
||||
"error: failed to remove {0}game_{1}{2} copy",
|
||||
GAME_LIB_DIR,
|
||||
api.api_version,
|
||||
LIB_EXT,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
reload_game_lib :: proc(hr: ^Hot_Reload) -> (lib: ^Game_API, err: Error) {
|
||||
api := active_lib(hr)
|
||||
force_reload := api.force_reload()
|
||||
force_restart := api.force_restart()
|
||||
reload := force_reload || force_restart
|
||||
|
||||
lib_mod, lib_mod_err := os.last_write_time_by_name(GAME_LIB_PATH)
|
||||
// TODO: Handle error
|
||||
if lib_mod_err == os.ERROR_NONE && api.modification_time != lib_mod {
|
||||
reload = true
|
||||
}
|
||||
|
||||
if !reload do return api, nil
|
||||
|
||||
new_api: Game_API
|
||||
new_api.api_version = api.api_version + 1
|
||||
new_api_ok := load_game_lib(&new_api)
|
||||
if !new_api_ok {
|
||||
fmt.println("error: failed to load Game API")
|
||||
return nil, .Load_Game_Lib_Failed
|
||||
}
|
||||
|
||||
force_restart = force_restart || api.memory_size() != new_api.memory_size()
|
||||
|
||||
if !force_restart {
|
||||
// This does the normal hot reload
|
||||
|
||||
// Note that we don't unload the old game APIs because that
|
||||
// would unload the lib. The lib can contain stored info
|
||||
// such as string literals. The old libs are only unloaded
|
||||
// on a full reset or on shutdown.
|
||||
memory := api.memory()
|
||||
new_api.memory_set(memory)
|
||||
} else {
|
||||
// This does a full reset. That's basically like opening and
|
||||
// closing the game, without having to restart the executable.
|
||||
//
|
||||
// You end up in here if the game requests a full reset OR
|
||||
// if the size of the game memory has changed. That would
|
||||
// probably lead to a crash anyways.
|
||||
|
||||
api.memory_free()
|
||||
|
||||
for &g in hr.loaded_libs {
|
||||
unload_game_lib(&g)
|
||||
}
|
||||
clear(&hr.loaded_libs)
|
||||
|
||||
new_api.init()
|
||||
}
|
||||
|
||||
append(&hr.loaded_libs, new_api)
|
||||
|
||||
return active_lib(hr), nil
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package base
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Button/Key states
|
||||
Action :: enum i8 {
|
||||
None = -1,
|
||||
Release = 0,
|
||||
Press = 1,
|
||||
Repeat = 2,
|
||||
}
|
||||
|
||||
Key :: enum i16 {
|
||||
// The unknown key
|
||||
Key_Unknown = -1,
|
||||
|
||||
// --- Printable keys ---
|
||||
|
||||
// Named printable keys
|
||||
Key_Space = 32,
|
||||
Key_Apostrophe = 39, // '
|
||||
Key_Comma = 44, // ,
|
||||
Key_Minus = 45, // -
|
||||
Key_Period = 46, // .
|
||||
Key_Slash = 47, // /
|
||||
Key_Semicolon = 59, // ;
|
||||
Key_Equal = 61, // =
|
||||
Key_Left_Bracket = 91, // [
|
||||
Key_Backslash = 92, // \
|
||||
Key_Right_Bracket = 93, // ]
|
||||
Key_Grave_Accent = 96, // `
|
||||
Key_World_1 = 161, // non-US #1
|
||||
Key_World_2 = 162, // non-US #2
|
||||
|
||||
// Alphanumeric characters
|
||||
Key_0 = 48,
|
||||
Key_1 = 49,
|
||||
Key_2 = 50,
|
||||
Key_3 = 51,
|
||||
Key_4 = 52,
|
||||
Key_5 = 53,
|
||||
Key_6 = 54,
|
||||
Key_7 = 55,
|
||||
Key_8 = 56,
|
||||
Key_9 = 57,
|
||||
Key_A = 65,
|
||||
Key_B = 66,
|
||||
Key_C = 67,
|
||||
Key_D = 68,
|
||||
Key_E = 69,
|
||||
Key_F = 70,
|
||||
Key_G = 71,
|
||||
Key_H = 72,
|
||||
Key_I = 73,
|
||||
Key_J = 74,
|
||||
Key_K = 75,
|
||||
Key_L = 76,
|
||||
Key_M = 77,
|
||||
Key_N = 78,
|
||||
Key_O = 79,
|
||||
Key_P = 80,
|
||||
Key_Q = 81,
|
||||
Key_R = 82,
|
||||
Key_S = 83,
|
||||
Key_T = 84,
|
||||
Key_U = 85,
|
||||
Key_V = 86,
|
||||
Key_W = 87,
|
||||
Key_X = 88,
|
||||
Key_Y = 89,
|
||||
Key_Z = 90,
|
||||
|
||||
// --- Function keys ---
|
||||
|
||||
// Named non-printable keys
|
||||
Key_Escape = 256,
|
||||
Key_Enter = 257,
|
||||
Key_Tab = 258,
|
||||
Key_Backspace = 259,
|
||||
Key_Insert = 260,
|
||||
Key_Delete = 261,
|
||||
Key_Right = 262,
|
||||
Key_Left = 263,
|
||||
Key_Down = 264,
|
||||
Key_Up = 265,
|
||||
Key_Page_Up = 266,
|
||||
Key_Page_Down = 267,
|
||||
Key_Home = 268,
|
||||
Key_End = 269,
|
||||
Key_Caps_Lock = 280,
|
||||
Key_Scroll_Lock = 281,
|
||||
Key_Num_Lock = 282,
|
||||
Key_Print_Screen = 283,
|
||||
Key_Pause = 284,
|
||||
|
||||
// Function keys
|
||||
Key_F1 = 290,
|
||||
Key_F2 = 291,
|
||||
Key_F3 = 292,
|
||||
Key_F4 = 293,
|
||||
Key_F5 = 294,
|
||||
Key_F6 = 295,
|
||||
Key_F7 = 296,
|
||||
Key_F8 = 297,
|
||||
Key_F9 = 298,
|
||||
Key_F10 = 299,
|
||||
Key_F11 = 300,
|
||||
Key_F12 = 301,
|
||||
Key_F13 = 302,
|
||||
Key_F14 = 303,
|
||||
Key_F15 = 304,
|
||||
Key_F16 = 305,
|
||||
Key_F17 = 306,
|
||||
Key_F18 = 307,
|
||||
Key_F19 = 308,
|
||||
Key_F20 = 309,
|
||||
Key_F21 = 310,
|
||||
Key_F22 = 311,
|
||||
Key_F23 = 312,
|
||||
Key_F24 = 313,
|
||||
Key_F25 = 314,
|
||||
|
||||
// Keypad numbers
|
||||
Key_KP_0 = 320,
|
||||
Key_KP_1 = 321,
|
||||
Key_KP_2 = 322,
|
||||
Key_KP_3 = 323,
|
||||
Key_KP_4 = 324,
|
||||
Key_KP_5 = 325,
|
||||
Key_KP_6 = 326,
|
||||
Key_KP_7 = 327,
|
||||
Key_KP_8 = 328,
|
||||
Key_KP_9 = 329,
|
||||
|
||||
// Keypad named function keys
|
||||
Key_KP_Decimal = 330,
|
||||
Key_KP_Divide = 331,
|
||||
Key_KP_Multiply = 332,
|
||||
Key_KP_Subtract = 333,
|
||||
Key_KP_Add = 334,
|
||||
Key_KP_Enter = 335,
|
||||
Key_KP_Equal = 336,
|
||||
|
||||
// Modifier keys
|
||||
Key_Left_Shift = 340,
|
||||
Key_Left_Control = 341,
|
||||
Key_Left_Alt = 342,
|
||||
Key_Left_Super = 343,
|
||||
Key_Right_Shift = 344,
|
||||
Key_Right_Control = 345,
|
||||
Key_Right_Alt = 346,
|
||||
Key_Right_Super = 347,
|
||||
Key_Menu = 348,
|
||||
Key_Last = Key_Menu,
|
||||
}
|
||||
|
||||
// Bitmask for modifier keys
|
||||
Mod :: enum i8 {
|
||||
Shift = 0,
|
||||
Control = 1,
|
||||
Alt = 2,
|
||||
Super = 3,
|
||||
Caps_Lock = 4,
|
||||
Num_Lock = 5,
|
||||
}
|
||||
Mod_Set :: bit_set[Mod]
|
||||
|
||||
// Mouse buttons
|
||||
Mouse_Button :: enum i8 {
|
||||
Button_1 = 0,
|
||||
Button_2 = 1,
|
||||
Button_3 = 2,
|
||||
Button_4 = 3,
|
||||
Button_5 = 4,
|
||||
Button_6 = 5,
|
||||
Button_7 = 6,
|
||||
Button_8 = 7,
|
||||
// Alias names
|
||||
Last = Button_8,
|
||||
Left = Button_1,
|
||||
Right = Button_2,
|
||||
Middle = Button_3,
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package base
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Settings :: struct {
|
||||
width: u16,
|
||||
height: u16,
|
||||
title: string,
|
||||
mode: WindowMode,
|
||||
refresh: u16, // 0 = unlimited
|
||||
vsync: bool,
|
||||
// TODO:
|
||||
// windowed resizable y/n
|
||||
// exit key (ex: escape)
|
||||
}
|
||||
|
||||
WindowMode :: enum u8 {
|
||||
Windowed = 0,
|
||||
Borderless = 1,
|
||||
Fullscreen = 2,
|
||||
}
|
||||
Reference in New Issue
Block a user