package main import rl "libraries/raylib" import "core:log" import "libraries/back" import "core:mem" import "core:c" import "base:intrinsics" import "base:runtime" import "core:strings" import "core:fmt" when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { @(private="file") web_context: runtime.Context @export main_start :: proc "c" () { context = runtime.default_context() // The WASM allocator doesn't seem to work properly in combination with // emscripten. There is some kind of conflict with how the manage memory. // So this sets up an allocator that uses emscripten's malloc. context.allocator = emscripten_allocator() runtime.init_global_temporary_allocator(1*mem.Megabyte) // Since we now use js_wasm32 we should be able to remove this and use // context.logger = log.create_console_logger(). However, that one produces // extra newlines on web. So it's a bug in that core lib. context.logger = create_emscripten_logger() web_context = context init() } @export main_update :: proc "c" () -> bool { context = web_context update() return should_run() } @export main_end :: proc "c" () { context = web_context shutdown() } @export web_window_size_changed :: proc "c" (w: c.int, h: c.int) { context = web_context parent_window_size_changed(int(w), int(h)) } /////////////////////////////////////////////////////// /// EMSCRIPTEN ALLOCATOR /// /////////////////////////////////////////////////////// // This will create bindings to emscripten's implementation of libc // memory allocation features. @(default_calling_convention = "c") foreign { calloc :: proc(num, size: c.size_t) -> rawptr --- free :: proc(ptr: rawptr) --- malloc :: proc(size: c.size_t) -> rawptr --- realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- } emscripten_allocator :: proc "contextless" () -> mem.Allocator { return mem.Allocator{emscripten_allocator_proc, nil} } emscripten_allocator_proc :: proc(allocator_data: rawptr,mode: mem.Allocator_Mode,size, alignment: int,old_memory: rawptr,old_size: int,location := #caller_location) -> (data: []byte, err: mem.Allocator_Error) { // These aligned alloc procs are almost indentical those in // `_heap_allocator_proc` in `core:os`. Without the proper alignment you // cannot use maps and simd features. aligned_alloc :: proc(size, alignment: int, zero_memory: bool, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { a := max(alignment, align_of(rawptr)) space := size + a - 1 allocated_mem: rawptr if old_ptr != nil { original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ allocated_mem = realloc(original_old_ptr, c.size_t(space+size_of(rawptr))) } else if zero_memory { // calloc automatically zeros memory, but it takes a number + size // instead of just size. allocated_mem = calloc(c.size_t(space+size_of(rawptr)), 1) } else { allocated_mem = malloc(c.size_t(space+size_of(rawptr))) } aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) ptr := uintptr(aligned_mem) aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) diff := int(aligned_ptr - ptr) if (size + diff) > space || allocated_mem == nil { return nil, .Out_Of_Memory } aligned_mem = rawptr(aligned_ptr) mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem return mem.byte_slice(aligned_mem, size), nil } aligned_free :: proc(p: rawptr) { if p != nil { free(mem.ptr_offset((^rawptr)(p), -1)^) } } aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, mem.Allocator_Error) { if p == nil { return nil, nil } return aligned_alloc(new_size, new_alignment, true, p) } switch mode { case .Alloc: return aligned_alloc(size, alignment, true) case .Alloc_Non_Zeroed: return aligned_alloc(size, alignment, false) case .Free: aligned_free(old_memory) return nil, nil case .Resize: if old_memory == nil { return aligned_alloc(size, alignment, true) } bytes := aligned_resize(old_memory, old_size, size, alignment) or_return // realloc doesn't zero the new bytes, so we do it manually. if size > old_size { new_region := raw_data(bytes[old_size:]) intrinsics.mem_zero(new_region, size - old_size) } return bytes, nil case .Resize_Non_Zeroed: if old_memory == nil { return aligned_alloc(size, alignment, false) } return aligned_resize(old_memory, old_size, size, alignment) case .Query_Features: set := (^mem.Allocator_Mode_Set)(old_memory) if set != nil { set^ = {.Alloc, .Free, .Resize, .Query_Features} } return nil, nil case .Free_All, .Query_Info: return nil, .Mode_Not_Implemented } return nil, .Mode_Not_Implemented } /////////////////////////////////////////////////////// /// EMSCRIPTEN LOGGER /// /////////////////////////////////////////////////////// Emscripten_Logger_Opts :: log.Options{.Level, .Short_File_Path, .Line} create_emscripten_logger :: proc (lowest := log.Level.Debug, opt := Emscripten_Logger_Opts) -> log.Logger { return log.Logger{data = nil, procedure = logger_proc, lowest_level = lowest, options = opt} } // This create's a binding to `puts` which will be linked in as part of the // emscripten runtime. @(default_calling_convention = "c") foreign { puts :: proc(buffer: cstring) -> c.int --- } @(private="file") logger_proc :: proc(logger_data: rawptr,level: log.Level,text: string,options: log.Options,location := #caller_location) { b := strings.builder_make(context.temp_allocator) strings.write_string(&b, Level_Headers[level]) do_location_header(options, &b, location) fmt.sbprint(&b, text) if bc, bc_err := strings.to_cstring(&b); bc_err == nil { puts(bc) } } @(private="file") Level_Headers := [?]string { 0 ..< 10 = "[DEBUG] --- ", 10 ..< 20 = "[INFO ] --- ", 20 ..< 30 = "[WARN ] --- ", 30 ..< 40 = "[ERROR] --- ", 40 ..< 50 = "[FATAL] --- ", } @(private="file") do_location_header :: proc(opts: log.Options, buf: ^strings.Builder, location := #caller_location) { if log.Location_Header_Opts & opts == nil { return } fmt.sbprint(buf, "[") file := location.file_path if .Short_File_Path in opts { last := 0 for r, i in location.file_path { if r == '/' { last = i + 1 } } file = location.file_path[last:] } if log.Location_File_Opts & opts != nil { fmt.sbprint(buf, file) } if .Line in opts { if log.Location_File_Opts & opts != nil { fmt.sbprint(buf, ":") } fmt.sbprint(buf, location.line) } if .Procedure in opts { if (log.Location_File_Opts | {.Line}) & opts != nil { fmt.sbprint(buf, ":") } fmt.sbprintf(buf, "%s()", location.procedure) } fmt.sbprint(buf, "] ") } } else { main :: proc() { // exe_path := os.args[0] // exe_dir := filepath.dir(string(exe_path)) // os.set_working_directory(exe_dir) rl.ChangeDirectory(rl.GetApplicationDirectory()) back.register_segfault_handler() context.logger = log.create_console_logger() init() for should_run() { update() } shutdown() } }