Use physfs in raylib

This commit is contained in:
sergeypdev 2025-04-25 12:58:19 +04:00
parent 973ad9e285
commit 1095afb510
7 changed files with 170 additions and 20 deletions

View File

@ -29,13 +29,14 @@ case $(uname) in
if [ ! -d "linux" ]; then if [ ! -d "linux" ]; then
mkdir linux mkdir linux
cp -r $ROOT/vendor/raylib/linux/libraylib*.so* linux cp -r $ROOT/vendor/raylib/linux/libraylib*.so* linux
cp -r libs/physfs/libphysfs.so* linux
fi fi
;; ;;
esac esac
# Build the game. # Build the game.
echo "Building game$DLL_EXT" 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. # 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 mv game_tmp$DLL_EXT game$DLL_EXT

139
game/fs.odin Normal file
View File

@ -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)
}
}

View File

@ -1022,9 +1022,11 @@ game_update :: proc() -> bool {
} }
@(export) @(export)
game_init_window :: proc() { game_init_window :: proc(args: []string) {
tracy.SetThreadName("Main") tracy.SetThreadName("Main")
init_physfs(args)
rl.SetConfigFlags({.WINDOW_RESIZABLE, .VSYNC_HINT}) rl.SetConfigFlags({.WINDOW_RESIZABLE, .VSYNC_HINT})
rl.InitWindow(1280, 720, "Odin + Raylib + Hot Reload template!") rl.InitWindow(1280, 720, "Odin + Raylib + Hot Reload template!")
rl.SetExitKey(.KEY_NULL) rl.SetExitKey(.KEY_NULL)
@ -1056,6 +1058,8 @@ game_shutdown :: proc() {
@(export) @(export)
game_shutdown_window :: proc() { game_shutdown_window :: proc() {
rl.CloseWindow() rl.CloseWindow()
deinit_physfs()
} }
@(export) @(export)

View File

@ -1,3 +1,3 @@
*.a *.a
*.so *.so*
build build

View File

@ -5,4 +5,4 @@ build_physfs: configure_cmake
libphysfs.a: build_physfs libphysfs.a: build_physfs
cp ./build/libphysfs.a . cp ./build/libphysfs.a .
libphysfs.so: build_physfs libphysfs.so: build_physfs
cp ./build/libphysfs.so . cp ./build/libphysfs.so* .

View File

@ -2,9 +2,15 @@ package physfs
import "core:c" import "core:c"
PHYSFS_SHARED :: #config(PHYSFS_SHARED, false)
when ODIN_OS == .Linux || ODIN_OS == .Darwin { when ODIN_OS == .Linux || ODIN_OS == .Darwin {
when PHYSFS_SHARED {
foreign import lib "libphysfs.so"
} else {
foreign import lib "libphysfs.a" foreign import lib "libphysfs.a"
} }
}
@(default_calling_convention = "c", link_prefix = "PHYSFS_") @(default_calling_convention = "c", link_prefix = "PHYSFS_")
foreign lib { foreign lib {
@ -69,7 +75,7 @@ foreign lib {
mount :: proc(newDir, mountPoint: cstring, appendToPath: c.int) -> c.int --- mount :: proc(newDir, mountPoint: cstring, appendToPath: c.int) -> c.int ---
unmount :: proc(oldDir: cstring) -> c.int --- unmount :: proc(oldDir: cstring) -> c.int ---
mountIo :: proc(io: ^Io, newdir, mountPoint: cstring, appendToPath: c.int) -> 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 --- mountHandle :: proc(file: ^File, newDir, mountPoint: cstring, appendToPath: c.int) -> c.int ---
getMountPoint :: proc(dir: cstring) -> cstring --- getMountPoint :: proc(dir: cstring) -> cstring ---

View File

@ -40,7 +40,7 @@ copy_dll :: proc(to: string) -> bool {
Game_API :: struct { Game_API :: struct {
lib: dynlib.Library, lib: dynlib.Library,
init_window: proc(), init_window: proc(args: []string),
init: proc(), init: proc(),
update: proc() -> bool, update: proc() -> bool,
shutdown: proc(), shutdown: proc(),
@ -136,7 +136,7 @@ main :: proc() {
} }
game_api_version += 1 game_api_version += 1
game_api.init_window() game_api.init_window(os.args)
game_api.init() game_api.init()
old_game_apis := make([dynamic]Game_API, default_allocator) old_game_apis := make([dynamic]Game_API, default_allocator)