Compare commits
2 commits
48b365407f
...
5562037b1d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5562037b1d | ||
|
|
c3faaea8ce |
4 changed files with 469 additions and 0 deletions
72
memory.jai
72
memory.jai
|
|
@ -64,6 +64,78 @@ make :: ($T: Type, $init := true) -> *T
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Crash_Allocator :: Allocator.{ proc = crash_allocator_proc };
|
||||||
|
|
||||||
|
crash_allocator_proc :: (mode: Allocator_Mode, size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void {
|
||||||
|
message: string;
|
||||||
|
if mode == {
|
||||||
|
case .ALLOCATE;
|
||||||
|
message = basic.tprint("Attempt to allocate % byte(s) using the crash allocator!", size);
|
||||||
|
case .RESIZE;
|
||||||
|
message = basic.tprint("Attempt to resize (from % to % byte(s)) using the crash allocator!", old_size, size);
|
||||||
|
case .FREE;
|
||||||
|
message = basic.tprint("Attempt to free % byte(s) using the crash allocator!", size);
|
||||||
|
}
|
||||||
|
|
||||||
|
basic.assert(false, message);
|
||||||
|
debug_break();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Arena :: struct {
|
||||||
|
memory: *void;
|
||||||
|
memory_size: u64;
|
||||||
|
offset: u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_arena :: (a: *Arena, memory: *void, size: u64) {
|
||||||
|
a.memory = memory;
|
||||||
|
a.memory_size = size;
|
||||||
|
a.offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_allocator_proc :: (mode: Allocator_Mode, size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void {
|
||||||
|
arena := allocator_data.(*Arena);
|
||||||
|
if mode == {
|
||||||
|
case .ALLOCATE;
|
||||||
|
return arena_alloc(arena, size);
|
||||||
|
|
||||||
|
case .RESIZE;
|
||||||
|
if old_memory == null {
|
||||||
|
return arena_alloc(arena, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if size == 0 {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if size == old_size {
|
||||||
|
return old_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_memory := arena_alloc(arena, size);
|
||||||
|
memcpy(new_memory, old_memory, old_size);
|
||||||
|
return new_memory;
|
||||||
|
case;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_alloc :: (a: *Arena, count: int, alignment := Default_Align, loc := #caller_location) -> *void {
|
||||||
|
basic.assert(a.memory != null, "arena: not initialized", loc = loc);
|
||||||
|
basic.assert(power_of_two(alignment));
|
||||||
|
|
||||||
|
end := a.memory.(*u8) + a.offset;
|
||||||
|
ptr := align_forward(end.(uint), alignment);
|
||||||
|
total_size := (count + ptr.(*u8) - end.(*u8)).(u64);
|
||||||
|
|
||||||
|
basic.assert(a.offset + total_size <= a.memory_size, "arena: out of memory", loc = loc);
|
||||||
|
a.offset += total_size;
|
||||||
|
|
||||||
|
return ptr.(*void);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#scope_file;
|
#scope_file;
|
||||||
|
|
||||||
|
|
|
||||||
41
reload/examples/everything-you-need.jai
Normal file
41
reload/examples/everything-you-need.jai
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
G: *struct {
|
||||||
|
allocator: Allocator;
|
||||||
|
iterations: int;
|
||||||
|
};
|
||||||
|
|
||||||
|
#program_export max_memory: u64 = 4 * Gigabyte;
|
||||||
|
#program_export state_size: u64 = size_of(type_of(G.*));
|
||||||
|
|
||||||
|
#program_export init :: (state: *void, allocator: Allocator, reset: bool) {
|
||||||
|
print("in: init\n");
|
||||||
|
|
||||||
|
G = xx state;
|
||||||
|
G.allocator = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
#program_export setup :: () {
|
||||||
|
print("in: setup\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#program_export teardown :: () {
|
||||||
|
print("in: teardown\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#program_export frame :: () -> reload.Status {
|
||||||
|
G.iterations += 1; // change this line and rebuild the program library
|
||||||
|
|
||||||
|
print("in frame, count: %\n", G.iterations);
|
||||||
|
sleep_milliseconds(1000);
|
||||||
|
return .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#import "Basic";
|
||||||
|
|
||||||
|
#import "jx";
|
||||||
|
|
||||||
|
#poke_name reload frame;
|
||||||
|
#poke_name reload init;
|
||||||
|
#poke_name reload setup;
|
||||||
|
#poke_name reload teardown;
|
||||||
|
|
||||||
|
reload :: #import "jx/reload";
|
||||||
127
reload/module.jai
Normal file
127
reload/module.jai
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
#module_parameters(disable := false, loc := #caller_location);
|
||||||
|
|
||||||
|
Status :: enum {
|
||||||
|
none;
|
||||||
|
quit;
|
||||||
|
soft_reload;
|
||||||
|
hard_reload;
|
||||||
|
}
|
||||||
|
|
||||||
|
Init_Proc :: #type (*void, Allocator, bool);
|
||||||
|
Setup_Proc :: #type ();
|
||||||
|
Teardown_Proc :: #type ();
|
||||||
|
Frame_Proc :: #type () -> Status;
|
||||||
|
|
||||||
|
|
||||||
|
#scope_module;
|
||||||
|
|
||||||
|
#placeholder frame;
|
||||||
|
#placeholder init;
|
||||||
|
#placeholder setup;
|
||||||
|
#placeholder teardown;
|
||||||
|
|
||||||
|
// Ensure laptops use the higher performance GPU on Windows.
|
||||||
|
#if OS == .WINDOWS {
|
||||||
|
/*
|
||||||
|
https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm
|
||||||
|
|
||||||
|
Starting with the Release 302 drivers, application developers can direct the Optimus driver at runtime to use the High Performance Graphics to render any application —- even those applications for which there is no existing application profile. They can do this by exporting a global variable named NvOptimusEnablement. The Optimus driver looks for the existence and value of the export. Only the LSB of the DWORD matters at this time. A value of 0x00000001 indicates that rendering should be performed using High Performance Graphics. A value of 0x00000000 indicates that this method should be ignored.
|
||||||
|
*/
|
||||||
|
#program_export NvOptimusEnablement: u32 = 0x00000001;
|
||||||
|
|
||||||
|
/*
|
||||||
|
https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/
|
||||||
|
|
||||||
|
Many gaming and workstation laptops are available with both (1) integrated power saving and (2) discrete high performance graphics devices. Unfortunately, 3D intensive application performance may suffer greatly if the best graphics device is not selected. For example, a game may run at 30 Frames Per Second (FPS) on the integrated GPU rather than the 60 FPS the discrete GPU would enable. As a developer you can easily fix this problem by adding only one line to your executable's source code:
|
||||||
|
*/
|
||||||
|
#program_export AmdPowerXpressRequestHighPerformance: u32 = 0x00000001;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !disable {
|
||||||
|
#load "reload_main.jai";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
#assert (type_of(init) == Init_Proc) "init was the wrong type!";
|
||||||
|
#assert (type_of(setup) == Setup_Proc) "setup was the wrong type!";
|
||||||
|
#assert (type_of(teardown) == Teardown_Proc) "teardown was the wrong type!";
|
||||||
|
#assert (type_of(frame) == Frame_Proc) "frame was the wrong type!";
|
||||||
|
|
||||||
|
file_path :: #run -> string { return loc.fully_pathed_filename; }
|
||||||
|
proj_path :: #run -> string { return path_strip_filename(file_path); }
|
||||||
|
temp_path :: #run -> string { return tprint("%/.build/tmp.jai", proj_path); };
|
||||||
|
temp_exists :: #run -> bool { return file_exists(temp_path); }
|
||||||
|
|
||||||
|
// only run the build when we the module importing us is compiled
|
||||||
|
#if !(disable || temp_exists) #run {
|
||||||
|
set_build_options_dc(.{ do_output = false });
|
||||||
|
|
||||||
|
make_directory_if_it_does_not_exist(".build");
|
||||||
|
|
||||||
|
out_path := path_strip_extension(proj_path);
|
||||||
|
out_name := path_strip_extension(path_filename(file_path));
|
||||||
|
|
||||||
|
// build target executable
|
||||||
|
{
|
||||||
|
ws := compiler_create_workspace(file_path);
|
||||||
|
defer compiler_destroy_workspace(ws);
|
||||||
|
|
||||||
|
opts := get_build_options(ws);
|
||||||
|
opts.text_output_flags = 0;
|
||||||
|
opts.output_path = out_path;
|
||||||
|
opts.output_type = .EXECUTABLE;
|
||||||
|
opts.output_executable_name = out_name;
|
||||||
|
|
||||||
|
tmp: String_Builder;
|
||||||
|
print_to_builder(*tmp, #string END
|
||||||
|
#load "%1";
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
(#import "jx/reload").reload_main();
|
||||||
|
}
|
||||||
|
END, file_path);
|
||||||
|
|
||||||
|
assert(write_entire_file(temp_path, builder_to_string(*tmp)));
|
||||||
|
|
||||||
|
compiler_begin_intercept(ws);
|
||||||
|
set_build_options(opts, ws);
|
||||||
|
|
||||||
|
add_build_file(temp_path, ws);
|
||||||
|
while true {
|
||||||
|
msg := compiler_wait_for_message();
|
||||||
|
if msg.kind == .COMPLETE || msg.kind == .ERROR break;
|
||||||
|
}
|
||||||
|
compiler_end_intercept(ws);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build library
|
||||||
|
{
|
||||||
|
ws := compiler_create_workspace(file_path);
|
||||||
|
defer compiler_destroy_workspace(ws);
|
||||||
|
|
||||||
|
opts := get_build_options(ws);
|
||||||
|
opts.output_path = out_path;
|
||||||
|
opts.output_type = .DYNAMIC_LIBRARY;
|
||||||
|
opts.output_executable_name = "lib";
|
||||||
|
|
||||||
|
compiler_begin_intercept(ws);
|
||||||
|
set_build_options(opts, ws);
|
||||||
|
|
||||||
|
add_build_file(temp_path, ws);
|
||||||
|
while true {
|
||||||
|
msg := compiler_wait_for_message();
|
||||||
|
if msg.kind == .COMPLETE || msg.kind == .ERROR break;
|
||||||
|
}
|
||||||
|
compiler_end_intercept(ws);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_delete(temp_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#import "Basic";
|
||||||
|
#import "File";
|
||||||
|
#import "File_Utilities";
|
||||||
|
#import "String";
|
||||||
|
#import "Compiler";
|
||||||
229
reload/reload_main.jai
Normal file
229
reload/reload_main.jai
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
reload_main :: () {
|
||||||
|
basic.set_working_directory(strings.path_strip_filename(system.get_path_of_running_executable()));
|
||||||
|
|
||||||
|
system_allocator := context.allocator;
|
||||||
|
|
||||||
|
// Ensure we're not allocating anywhere unexpected
|
||||||
|
context.allocator = Crash_Allocator;
|
||||||
|
|
||||||
|
lib_api, ok := load_library(0);
|
||||||
|
basic.assert(ok, "host: failed to load library");
|
||||||
|
|
||||||
|
lib_versions: [..]Library;
|
||||||
|
lib_versions.allocator = system_allocator;
|
||||||
|
|
||||||
|
H := lib_api;
|
||||||
|
next_lib_version := 1;
|
||||||
|
|
||||||
|
defer {
|
||||||
|
for * lib_versions {
|
||||||
|
unload_library(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
unload_library(*H);
|
||||||
|
basic.free(lib_versions.data,, allocator = system_allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("host: allocating % bytes memory (% for state)\n", H.max_memory, H.state_size);
|
||||||
|
|
||||||
|
lib_memory := basic.alloc(xx H.max_memory,, allocator = system_allocator);
|
||||||
|
assert(lib_memory != null, "host: failed to allocate memory!");
|
||||||
|
defer free(lib_memory,, allocator = system_allocator);
|
||||||
|
|
||||||
|
lib_arena: Arena;
|
||||||
|
init_arena(*lib_arena, lib_memory, H.max_memory);
|
||||||
|
|
||||||
|
lib_allocator := Allocator.{
|
||||||
|
proc = arena_allocator_proc,
|
||||||
|
data = *lib_arena,
|
||||||
|
};
|
||||||
|
|
||||||
|
lib_state := basic.alloc(xx H.state_size,, allocator = lib_allocator);
|
||||||
|
|
||||||
|
H.init(lib_state, lib_allocator, true);
|
||||||
|
H.setup();
|
||||||
|
|
||||||
|
while true {
|
||||||
|
defer reset_temporary_storage();
|
||||||
|
|
||||||
|
status := H.frame();
|
||||||
|
if status == .quit break;
|
||||||
|
|
||||||
|
reload := status == .soft_reload || status == .hard_reload;
|
||||||
|
if !reload {
|
||||||
|
path := basic.tprint(Default_Lib_Path, Lib_Extension);
|
||||||
|
mod := futils.file_modtime_and_size(path);
|
||||||
|
if mod != H.mod_time {
|
||||||
|
reload = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reload {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_api, ok := load_library(next_lib_version);
|
||||||
|
if !ok {
|
||||||
|
basic.print("host: failed to load version % of the library\n", next_lib_version);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_ssize := H.state_size;
|
||||||
|
old_msize := H.max_memory;
|
||||||
|
new_ssize := new_api.state_size;
|
||||||
|
new_msize := new_api.max_memory;
|
||||||
|
|
||||||
|
if old_ssize != new_ssize || old_msize != new_msize {
|
||||||
|
status = .hard_reload;
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == .hard_reload {
|
||||||
|
basic.print("host: performing hard reload...\n");
|
||||||
|
|
||||||
|
if old_msize <= new_msize {
|
||||||
|
lib_arena.offset = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
free(lib_memory,, allocator = system_allocator);
|
||||||
|
|
||||||
|
lib_memory = basic.alloc(xx new_msize,, allocator = system_allocator);
|
||||||
|
basic.assert(lib_memory != null, "host: failed to allocator new memory");
|
||||||
|
|
||||||
|
init_arena(*lib_arena, lib_memory, new_msize);
|
||||||
|
lib_state = basic.alloc(xx new_ssize,, allocator = lib_allocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
basic.print("host: performing soft reload...\n");
|
||||||
|
basic.array_add(*lib_versions, H);
|
||||||
|
}
|
||||||
|
|
||||||
|
H = new_api;
|
||||||
|
next_lib_version += 1;
|
||||||
|
|
||||||
|
H.init(lib_state, lib_allocator, status == .hard_reload);
|
||||||
|
}
|
||||||
|
|
||||||
|
H.teardown();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
Library :: struct {
|
||||||
|
max_memory: u64;
|
||||||
|
state_size: u64;
|
||||||
|
|
||||||
|
init: Init_Proc;
|
||||||
|
setup: Setup_Proc;
|
||||||
|
teardown: Teardown_Proc;
|
||||||
|
frame: Frame_Proc;
|
||||||
|
|
||||||
|
version: int;
|
||||||
|
handle: *void;
|
||||||
|
mod_time: basic.Apollo_Time;
|
||||||
|
}
|
||||||
|
|
||||||
|
Default_Lib_Path :: "lib.%";
|
||||||
|
Temp_Lib_Path :: "__temp_lib_%.%";
|
||||||
|
|
||||||
|
#if OS == {
|
||||||
|
case .WINDOWS;
|
||||||
|
Lib_Extension :: "dll";
|
||||||
|
case .MACOS;
|
||||||
|
Lib_Extension :: "dylib";
|
||||||
|
case .LINUX;
|
||||||
|
Lib_Extension :: "so";
|
||||||
|
}
|
||||||
|
|
||||||
|
load_library :: (version: int) -> Library, bool {
|
||||||
|
default_path := basic.tprint(Default_Lib_Path, Lib_Extension);
|
||||||
|
|
||||||
|
mod, size, exists := futils.file_modtime_and_size(default_path);
|
||||||
|
if !exists {
|
||||||
|
basic.print("host: % did not exist\n", default_path);
|
||||||
|
return .{}, false;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_path := basic.tprint(Temp_Lib_Path, version, Lib_Extension);
|
||||||
|
if !futils.copy_file(default_path, new_path,, allocator = basic.temp) {
|
||||||
|
basic.print("host: could not copy % to %\n", default_path, new_path);
|
||||||
|
return .{}, false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle := os_load_library(new_path);
|
||||||
|
if !handle {
|
||||||
|
basic.print("host: failed to load library at %\n", new_path);
|
||||||
|
return .{}, false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
max_memory = os_find_library_symbol(handle, "max_memory").(*u64).*,
|
||||||
|
state_size = os_find_library_symbol(handle, "state_size").(*u64).*,
|
||||||
|
init = os_find_library_symbol(handle, "init"),
|
||||||
|
setup = os_find_library_symbol(handle, "setup"),
|
||||||
|
teardown = os_find_library_symbol(handle, "teardown"),
|
||||||
|
frame = os_find_library_symbol(handle, "frame"),
|
||||||
|
|
||||||
|
version = version,
|
||||||
|
handle = handle,
|
||||||
|
mod_time = mod,
|
||||||
|
}, true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unload_library :: (library: *Library) {
|
||||||
|
path := basic.tprint(Temp_Lib_Path, library.version, Lib_Extension);
|
||||||
|
if library.handle && !os_unload_library(library.handle) {
|
||||||
|
basic.print("host: failed to unload temporary library: %\n", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !file.file_delete(path) {
|
||||||
|
basic.print("host: failed to delete temporary library: %\n", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if OS == .WINDOWS {
|
||||||
|
#import "Windows";
|
||||||
|
// @note(judah): Haven't tested this on my windows machine,
|
||||||
|
// but I remember things working like this.
|
||||||
|
|
||||||
|
os_load_library :: (path: string) -> *void {
|
||||||
|
handle := LoadLibraryA(temp_c_string(path));
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
os_unload_library :: (handle: *void) -> bool {
|
||||||
|
return FreeLibrary(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
os_find_library_symbol :: (handle: *void, symbol: string) -> *void {
|
||||||
|
return GetProcAddress(handle, temp_c_string(symbol));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else #if OS == .MACOS || OS == .LINUX {
|
||||||
|
#import "POSIX";
|
||||||
|
|
||||||
|
os_load_library :: (path: string) -> *void {
|
||||||
|
handle := dlopen(basic.temp_c_string(path), RTLD_NOW);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
os_unload_library :: (handle: *void) -> bool {
|
||||||
|
return dlclose(handle) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
os_find_library_symbol :: (handle: *void, symbol: string) -> *void {
|
||||||
|
return dlsym(handle, basic.temp_c_string(symbol));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#assert(false, "only windows, mac, and linux are supported for now");
|
||||||
|
}
|
||||||
|
|
||||||
|
#import "jx";
|
||||||
|
|
||||||
|
basic :: #import "Basic";
|
||||||
|
system :: #import "System";
|
||||||
|
file :: #import "File";
|
||||||
|
futils :: #import "File_Utilities";
|
||||||
|
strings :: #import "String";
|
||||||
Loading…
Reference in a new issue