1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! This module contains the entry points callable from JavaScript.
//!
//! # Usage
//!
//! To initialize the memory correctly, the exports have to be called in the following order:
//! 1. `exports.__wasm_init_memory()`
//! 2. `exports.__wasm_init_tls()`
//! 3. `exports.init()`
//! Only now other functions may get called.
//! Calling 1-3 more than once is undefined behavior.
//! When executing `init()` a message is sent to the main thread, signaling the initialization has
//! finished. This signal is used to start the graphics worker.

use crate::graphics::renderer;
use crate::logic::LogicContext;
#[cfg(target_arch = "wasm32")]
use crate::{
    communication::{
        Message, MessageQueue, SynchronizationMemory, MESSAGE_QUEUE_ELEMENT_COUNT,
        SYNCHRONIZATION_MEMORY,
    },
    mem,
    wasm_log::{init_panic_handler, WasmLog},
};
use linked_list_allocator::LockedHeap;

use nobg_web_worker::child_entry_point;
#[cfg(target_arch = "wasm32")]
use nobg_web_worker::set_global_thread_pool;

#[cfg(target_arch = "wasm32")]
static LOGGER: WasmLog = WasmLog;

#[cfg(target_arch = "wasm32")]
#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();

#[cfg(target_arch = "wasm32")]
extern "C" {
    fn spawn_graphics_worker(stack_top: u32, tls: u32);
}

/// This function initializes the heap, logger, panic handler and graphics context.
/// The sizes of static elements such as the resource_table can be set in `crate::mem`.
///
/// # Safety
///
/// This function may only be called once at the start of the program.
/// Any call to alloc prior to this functions invocation results in an error.
#[export_name = "init"]
#[cfg(target_arch = "wasm32")]
pub extern "C" fn init(heap_base: u32, mem_size: u32, tls_size: u32) -> u32 {
    unsafe {
        mem::set_tls_size(tls_size);
        ALLOCATOR
            .lock()
            .init(heap_base as usize, (mem_size - heap_base) as usize);
    }
    // set custom panic handler
    init_panic_handler();
    log::set_logger(&LOGGER).unwrap();
    // change the log level to only show certain errors
    log::set_max_level(log::LevelFilter::Debug);
    log::info!("mem_size: {}", mem_size - heap_base);
    mem::alloc_tls() as u32
}

fn spawn_threadpools() -> rayon::ThreadPool {
    #[cfg(target_arch = "wasm32")]
    {
        let global_worker_pool =
            set_global_thread_pool(4, mem::WORKER_STACK_SIZE as u32, mem::get_tls_size() as u32)
                .unwrap();
        #[cfg(target_arch = "wasm32")]
        let (pool, engine_workers) = nobg_web_worker::default_thread_pool(
            4,
            mem::WORKER_STACK_SIZE as u32,
            mem::get_tls_size() as u32,
        )
        .unwrap();
        Box::leak(Box::new((engine_workers, global_worker_pool)));
        return pool;
    }
    #[allow(unreachable_code)]
    rayon::ThreadPoolBuilder::new().build().unwrap()
}

/// Initialize the game state, communicate with the graphics worker and set up networking.
/// This function is being exposed to JavaScript.
#[export_name = "run_logic"]
pub extern "C" fn run_logic() {
    // create the logic game context
    let mut game = LogicContext::new(spawn_threadpools()).unwrap_or_else(|e| panic!("{}", e));

    #[cfg(target_arch = "wasm32")]
    {
        let syn_addr = unsafe { &SYNCHRONIZATION_MEMORY as *const SynchronizationMemory as u32 };
        // send memory offset to the main thread -> initialize graphics
        Message::Memory(
            syn_addr,
            &game.message_queue as *const MessageQueue as u32,
            MESSAGE_QUEUE_ELEMENT_COUNT as u32,
        )
        .send();
        let stack = mem::alloc_stack(mem::GRAPHICS_STACK_SIZE);
        let tls = mem::alloc_tls();
        log::debug!("spawn graphic");
        unsafe {
            spawn_graphics_worker(stack as u32, tls as u32);
        }
    }
    loop {
        game.tick()
            .unwrap_or_else(|e| log::error!("Error occurred game_context.tick(): {:?}", e));
        log::trace!("wait_for_main_thread_notify()");
        // use wasm's atomic wait instruction to sleep until waken by the main thread
        #[cfg(target_arch = "wasm32")]
        unsafe {
            SYNCHRONIZATION_MEMORY.wait_for_main_thread_notify()
        };
    }
}

/// This function is called to render each frame.
/// Most of the communication with the graphics API is done through calling JS functions.
#[export_name = "draw_frame"]
pub extern "C" fn draw_frame() {
    match unsafe { renderer::renderer_mut() } {
        // create a new graphics context if there is none, this persists local data across `draw_frame` invocations
        None => unsafe {
            renderer::set_renderer(
                renderer::Renderer::new()
                    .map_err(|e| panic!("{}", e))
                    .unwrap(),
            )
        },
        Some(ctx) => ctx,
    }
    .render()
    .unwrap_or_else(|e| log::error!("{}", e));
}

/// This function serves as the entry point for threadpool workers
#[export_name = "run_pool"]
pub unsafe extern "C" fn run_pool(ptr: u32) {
    child_entry_point(ptr);
}