diff --git a/build_hot_reload.sh b/build_hot_reload.sh index 1dabf5d..f0d772d 100755 --- a/build_hot_reload.sh +++ b/build_hot_reload.sh @@ -29,13 +29,14 @@ case $(uname) in if [ ! -d "linux" ]; then mkdir linux cp -r $ROOT/vendor/raylib/linux/libraylib*.so* linux + cp -r libs/physfs/libphysfs.so* linux fi ;; esac # Build the game. echo "Building game$DLL_EXT" -odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -define:TRACY_ENABLE=true -collection:libs=./libs -collection:common=./common -collection:game=./game -build-mode:dll -out:game_tmp$DLL_EXT -strict-style -vet -debug -o:speed +odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -define:PHYSFS_SHARED=true -define:TRACY_ENABLE=true -collection:libs=./libs -collection:common=./common -collection:game=./game -build-mode:dll -out:game_tmp$DLL_EXT -strict-style -vet -debug -o:speed # Need to use a temp file on Linux because it first writes an empty `game.so`, which the game will load before it is actually fully written. mv game_tmp$DLL_EXT game$DLL_EXT diff --git a/game/fs.odin b/game/fs.odin new file mode 100644 index 0000000..0cf843c --- /dev/null +++ b/game/fs.odin @@ -0,0 +1,139 @@ +package game + +import "base:runtime" +import "core:c" +import "core:log" +import "core:strings" +import "libs:physfs" +import rl "vendor:raylib" + +@(private = "file") +_ctx: runtime.Context + +init_physfs :: proc(args: []string) { + _ctx = context + + physfs.init(strings.clone_to_cstring(args[0], context.temp_allocator)) + physfs.setSaneConfig("serega", "gutter-runner", "zip", 0, 1) + + rl.SetLoadFileDataCallback(raylib_load_file_data_physfs) + rl.SetSaveFileDataCallback(raylib_save_file_data_physfs) + rl.SetLoadFileTextCallback(raylib_load_file_text_physfs) + rl.SetSaveFileTextCallback(raylib_save_file_text_physfs) +} + +deinit_physfs :: proc() { + rl.SetLoadFileDataCallback(nil) + rl.SetSaveFileDataCallback(nil) + rl.SetLoadFileTextCallback(nil) + rl.SetSaveFileTextCallback(nil) + + physfs.deinit() +} + +@(private = "file") +raylib_load_file_data_physfs :: proc "c" (file_name: cstring, bytes_read: ^c.int) -> [^]u8 { + context = _ctx + + bytes_read^ = 0 + if physfs.exists(file_name) == 0 { + log.warnf("PHYSFS: Tried to load unexisting file '%s'", file_name) + return nil + } + + // Open up the file. + handle := physfs.openRead(file_name) + if handle == nil { + trace_physfs_error(file_name) + return nil + } + defer physfs.close(handle) + + // Check to see how large the file is. + size := physfs.fileLength(handle) + if (size == -1) { + log.warnf("PHYSFS: Cannot determine size of file '%s'", file_name) + return nil + } + + // Close safely when it's empty. + if (size == 0) { + return nil + } + + // Read the file, return if it's empty. + buffer := rl.MemAlloc(u32(size)) + read := physfs.readBytes(handle, buffer, u64(size)) + if read < 0 { + rl.MemFree(buffer) + trace_physfs_error(file_name) + return nil + } + + bytes_read^ = i32(read) + return cast([^]u8)buffer +} + +@(private = "file") +raylib_save_file_data_physfs :: proc "c" ( + file_name: cstring, + data: rawptr, + bytes_to_write: c.int, +) -> bool { + context = _ctx + // Protect against empty writes. + if bytes_to_write <= 0 { + return true + } + + // Open the file. + handle := physfs.openWrite(file_name) + if handle == nil { + trace_physfs_error(file_name) + return false + } + defer physfs.close(handle) + + // Write the data to the file handle. + if (physfs.writeBytes(handle, data, u64(bytes_to_write)) < 0) { + trace_physfs_error(file_name) + return false + } + + return true +} + +@(private = "file") +raylib_load_file_text_physfs :: proc "c" (fileName: cstring) -> [^]u8 { + bytesRead: i32 + data := raylib_load_file_data_physfs(fileName, &bytesRead) + if bytesRead == 0 { + return nil + } + defer rl.MemFree(data) + + // Copy the data, and append a null terminator. + text := cast([^]u8)rl.MemAlloc(u32(bytesRead + 1)) + for i := i32(0); i < bytesRead; i += 1 { + text[i] = data[i] + } + text[bytesRead] = 0 + + return text +} + +@(private = "file") +raylib_save_file_text_physfs :: proc "c" (fileName: cstring, text: cstring) -> bool { + return raylib_save_file_data_physfs(fileName, cast(rawptr)text, i32(runtime.cstring_len(text))) +} + +@(private = "file") +trace_physfs_error :: proc(detail: cstring) { + errorCode := physfs.getLastErrorCode() + if errorCode == .OK { + log.warnf("PHYSFS: %s", detail) + } else { + errorMessage := physfs.getErrorByCode(errorCode) + log.warnf("PHYSFS: %s (%s)", errorMessage, detail) + } +} diff --git a/game/game.odin b/game/game.odin index 405b814..0a9a04d 100644 --- a/game/game.odin +++ b/game/game.odin @@ -1022,9 +1022,11 @@ game_update :: proc() -> bool { } @(export) -game_init_window :: proc() { +game_init_window :: proc(args: []string) { tracy.SetThreadName("Main") + init_physfs(args) + rl.SetConfigFlags({.WINDOW_RESIZABLE, .VSYNC_HINT}) rl.InitWindow(1280, 720, "Odin + Raylib + Hot Reload template!") rl.SetExitKey(.KEY_NULL) @@ -1056,6 +1058,8 @@ game_shutdown :: proc() { @(export) game_shutdown_window :: proc() { rl.CloseWindow() + + deinit_physfs() } @(export) diff --git a/libs/physfs/.gitignore b/libs/physfs/.gitignore index 31867af..566639a 100644 --- a/libs/physfs/.gitignore +++ b/libs/physfs/.gitignore @@ -1,3 +1,3 @@ *.a -*.so +*.so* build diff --git a/libs/physfs/Makefile b/libs/physfs/Makefile index 7fac115..bf33716 100644 --- a/libs/physfs/Makefile +++ b/libs/physfs/Makefile @@ -5,4 +5,4 @@ build_physfs: configure_cmake libphysfs.a: build_physfs cp ./build/libphysfs.a . libphysfs.so: build_physfs - cp ./build/libphysfs.so . + cp ./build/libphysfs.so* . diff --git a/libs/physfs/physfs.odin b/libs/physfs/physfs.odin index 58a3f85..3a99264 100644 --- a/libs/physfs/physfs.odin +++ b/libs/physfs/physfs.odin @@ -2,18 +2,24 @@ package physfs import "core:c" +PHYSFS_SHARED :: #config(PHYSFS_SHARED, false) + when ODIN_OS == .Linux || ODIN_OS == .Darwin { - foreign import lib "libphysfs.a" + when PHYSFS_SHARED { + foreign import lib "libphysfs.so" + } else { + foreign import lib "libphysfs.a" + } } -@(default_calling_convention="c", link_prefix="PHYSFS_") +@(default_calling_convention = "c", link_prefix = "PHYSFS_") foreign lib { init :: proc(argv0: cstring) -> c.int --- isInit :: proc() -> c.int --- deinit :: proc() -> c.int --- supportedArchiveTypes :: proc() -> [^]^ArchiveInfo --- freeList :: proc(listVar: rawptr) --- - + getLastError :: proc() -> cstring --- getDirSeparator :: proc() -> cstring --- permitSymbolicLinks :: proc(allow: c.int) --- @@ -26,10 +32,10 @@ foreign lib { removeFromSearchPath :: proc(oldDir: cstring) -> c.int --- getSearchPath :: proc() -> [^]cstring --- setSaneConfig :: proc(organization, appName, archiveExt: cstring, includedCdRoms, archivesFirst: c.int) -> c.int --- - + mkdir :: proc(dirName: cstring) -> c.int --- delete :: proc(filename: cstring) -> c.int --- - + getRealDir :: proc(filename: cstring) -> cstring --- getPrefDir :: proc(org, app: cstring) -> cstring --- enumerateFiles :: proc(dir: cstring) -> [^]cstring --- @@ -41,11 +47,11 @@ foreign lib { getLastErrorCode :: proc() -> ErrorCode --- getErrorByCode :: proc(code: ErrorCode) -> cstring --- setErrorCode :: proc(code: ErrorCode) --- - + openWrite :: proc(filename: cstring) -> ^File --- openAppend :: proc(filename: cstring) -> ^File --- openRead :: proc(filename: cstring) -> ^File --- - + close :: proc(handle: ^File) -> c.int --- read :: proc(handle: ^File, buffer: rawptr, objSize, objCount: uint32) -> sint64 --- write :: proc(handle: ^File, buffer: rawptr, objSize, objCount: uint32) -> sint64 --- @@ -58,26 +64,26 @@ foreign lib { flush :: proc(handle: ^File) -> c.int --- readBytes :: proc(handle: ^File, buffer: rawptr, len: uint64) -> sint64 --- writeBytes :: proc(handle: ^File, buffer: rawptr, len: uint64) -> sint64 --- - + registerArchiver :: proc(archiver: ^Archiver) -> c.int --- deregisterArchiver :: proc(ext: cstring) -> c.int --- - + setRoot :: proc(archive, subdir: cstring) -> c.int --- setAllocator :: proc(allocator: ^Allocator) -> c.int --- getAllocator :: proc() -> ^Allocator --- - + mount :: proc(newDir, mountPoint: cstring, appendToPath: c.int) -> c.int --- unmount :: proc(oldDir: cstring) -> c.int --- mountIo :: proc(io: ^Io, newdir, mountPoint: cstring, appendToPath: c.int) -> c.int --- - mountMemory :: proc(buf: rawptr, len: uint64, del: proc "c"(rawptr), newDir, mountPoint: cstring, appendToPath: c.int) -> c.int --- + mountMemory :: proc(buf: rawptr, len: uint64, del: proc "c" (_: rawptr), newDir, mountPoint: cstring, appendToPath: c.int) -> c.int --- mountHandle :: proc(file: ^File, newDir, mountPoint: cstring, appendToPath: c.int) -> c.int --- - + getMountPoint :: proc(dir: cstring) -> cstring --- getCdRomDirsCallback :: proc(c: StringCallback, d: rawptr) --- getSearchPathCallback :: proc(c: StringCallback, d: rawptr) --- enumerateFilesCallback :: proc(dir: cstring, c: EnumFilesCallback, d: rawptr) --- enumerate :: proc(dir: cstring, callback: EnumerateCallback, d: rawptr) -> c.int --- - + swapSLE16 :: proc(val: sint16) -> sint16 --- swapULE16 :: proc(val: uint16) -> uint16 --- swapLE32 :: proc(val: sint32) -> sint32 --- @@ -124,7 +130,7 @@ foreign lib { utf8FromLatin1 :: proc(src, dst: cstring, len: uint64) --- utf8FromUtf16 :: proc(src: ^uint16, dst: cstring, len: uint64) --- utf8ToUtf16 :: proc(src: cstring, dst: ^uint16, len: uint64) --- - + caseFold :: proc(from: uint32, to: ^uint32) -> c.int --- utf8stricmp :: proc(str1, str2: cstring) -> c.int --- utf16stricmp :: proc(str1, str2: cstring) -> c.int --- diff --git a/main_hot_reload/main_hot_reload.odin b/main_hot_reload/main_hot_reload.odin index 39e9818..ae121bc 100644 --- a/main_hot_reload/main_hot_reload.odin +++ b/main_hot_reload/main_hot_reload.odin @@ -40,7 +40,7 @@ copy_dll :: proc(to: string) -> bool { Game_API :: struct { lib: dynlib.Library, - init_window: proc(), + init_window: proc(args: []string), init: proc(), update: proc() -> bool, shutdown: proc(), @@ -136,7 +136,7 @@ main :: proc() { } game_api_version += 1 - game_api.init_window() + game_api.init_window(os.args) game_api.init() old_game_apis := make([dynamic]Game_API, default_allocator)