273 lines
8.5 KiB
Rust
273 lines
8.5 KiB
Rust
// Browser integration via our own miniquad JS plugin (www/dc_plugin.js):
|
|
// persistent key/value storage (localStorage), a cache that is written to storage when the
|
|
// page is hidden or closed, file download, file upload and HTTP requests.
|
|
// Native builds use simple fallbacks.
|
|
|
|
// Result of an HTTP request: status 0 means the service could not be reached
|
|
pub struct HttpResult {
|
|
pub status: i32,
|
|
pub body: Vec<u8>,
|
|
}
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
mod imp {
|
|
extern "C" {
|
|
fn dc_storage_get(key: *const u8, key_len: u32) -> i32;
|
|
fn dc_storage_take(dst: *mut u8);
|
|
fn dc_coarse_pointer() -> i32;
|
|
fn dc_open_url(u: *const u8, ul: u32);
|
|
fn dc_set_token(t: *const u8, tl: u32);
|
|
fn dc_fetch_download(u: *const u8, ul: u32, n: *const u8, nl: u32);
|
|
fn dc_fetch_download_state() -> i32;
|
|
fn dc_prompt(t: *const u8, tl: u32, d: *const u8, dl: u32) -> i32;
|
|
fn dc_storage_set(key: *const u8, key_len: u32, val: *const u8, val_len: u32);
|
|
fn dc_cache_set(key: *const u8, key_len: u32, val: *const u8, val_len: u32);
|
|
fn dc_download(name: *const u8, name_len: u32, data: *const u8, data_len: u32);
|
|
fn dc_open_file();
|
|
fn dc_file_poll() -> i32;
|
|
fn dc_file_take(dst: *mut u8);
|
|
fn dc_http(m: *const u8, ml: u32, u: *const u8, ul: u32, b: *const u8, bl: u32) -> u32;
|
|
fn dc_http_poll(id: u32) -> i32;
|
|
fn dc_http_len(id: u32) -> u32;
|
|
fn dc_http_take(id: u32, dst: *mut u8);
|
|
fn dc_ws_connect(u: *const u8, ul: u32) -> i32;
|
|
fn dc_ws_state(id: i32) -> i32;
|
|
fn dc_ws_send(id: i32, p: *const u8, pl: u32) -> i32;
|
|
fn dc_ws_take(id: i32) -> i32;
|
|
fn dc_ws_close(id: i32);
|
|
fn dc_renderer() -> i32;
|
|
}
|
|
|
|
// starts a request; poll the returned id with http_poll()
|
|
pub fn http(method: &str, url: &str, body: &[u8]) -> u32 {
|
|
unsafe { dc_http(method.as_ptr(), method.len() as u32, url.as_ptr(), url.len() as u32, body.as_ptr(), body.len() as u32) }
|
|
}
|
|
|
|
pub fn http_poll(id: u32) -> Option<super::HttpResult> {
|
|
unsafe {
|
|
let status = dc_http_poll(id);
|
|
if status == -2 {
|
|
return None;
|
|
}
|
|
let mut body = vec![0u8; dc_http_len(id) as usize];
|
|
dc_http_take(id, body.as_mut_ptr());
|
|
Some(super::HttpResult { status, body })
|
|
}
|
|
}
|
|
|
|
pub fn storage_get(key: &str) -> Option<String> {
|
|
unsafe {
|
|
let n = dc_storage_get(key.as_ptr(), key.len() as u32);
|
|
if n < 0 {
|
|
return None;
|
|
}
|
|
let mut buf = vec![0u8; n as usize];
|
|
dc_storage_take(buf.as_mut_ptr());
|
|
String::from_utf8(buf).ok()
|
|
}
|
|
}
|
|
|
|
// phones and tablets: the main pointer is a finger
|
|
pub fn coarse_pointer() -> bool {
|
|
unsafe { dc_coarse_pointer() != 0 }
|
|
}
|
|
|
|
pub fn open_url(url: &str) {
|
|
unsafe { dc_open_url(url.as_ptr(), url.len() as u32) }
|
|
}
|
|
|
|
// login session sent with every request to the service
|
|
pub fn set_token(token: &str) {
|
|
unsafe { dc_set_token(token.as_ptr(), token.len() as u32) }
|
|
}
|
|
|
|
// downloads a file of the server (with the login) straight to the user's disk
|
|
pub fn fetch_download(url: &str, name: &str) {
|
|
unsafe { dc_fetch_download(url.as_ptr(), url.len() as u32, name.as_ptr(), name.len() as u32) }
|
|
}
|
|
|
|
// 0 idle, 1 running, 2 done, 3 failed (2 and 3 are reported once)
|
|
pub fn fetch_download_state() -> i32 {
|
|
unsafe { dc_fetch_download_state() }
|
|
}
|
|
|
|
// asks for a text with the browser's dialog; None when cancelled
|
|
pub fn prompt(title: &str, default: &str) -> Option<String> {
|
|
unsafe {
|
|
let n = dc_prompt(title.as_ptr(), title.len() as u32, default.as_ptr(), default.len() as u32);
|
|
if n < 0 {
|
|
return None;
|
|
}
|
|
let mut buf = vec![0u8; n as usize];
|
|
dc_storage_take(buf.as_mut_ptr());
|
|
String::from_utf8(buf).ok()
|
|
}
|
|
}
|
|
|
|
pub fn storage_set(key: &str, val: &str) {
|
|
unsafe { dc_storage_set(key.as_ptr(), key.len() as u32, val.as_ptr(), val.len() as u32) }
|
|
}
|
|
|
|
// value is written to storage by JS when the page gets hidden or closed
|
|
pub fn cache_on_leave(key: &str, val: &str) {
|
|
unsafe { dc_cache_set(key.as_ptr(), key.len() as u32, val.as_ptr(), val.len() as u32) }
|
|
}
|
|
|
|
pub fn download(name: &str, data: &[u8]) {
|
|
unsafe { dc_download(name.as_ptr(), name.len() as u32, data.as_ptr(), data.len() as u32) }
|
|
}
|
|
|
|
// opens the browser file picker; poll with take_file()
|
|
pub fn open_file() {
|
|
unsafe { dc_open_file() }
|
|
}
|
|
|
|
pub fn take_file() -> Option<Vec<u8>> {
|
|
unsafe {
|
|
let n = dc_file_poll();
|
|
if n < 0 {
|
|
return None;
|
|
}
|
|
let mut buf = vec![0u8; n as usize];
|
|
dc_file_take(buf.as_mut_ptr());
|
|
Some(buf)
|
|
}
|
|
}
|
|
|
|
pub fn ws_connect(url: &str) -> i32 {
|
|
unsafe { dc_ws_connect(url.as_ptr(), url.len() as u32) }
|
|
}
|
|
|
|
pub fn ws_state(id: i32) -> i32 {
|
|
if id <= 0 {
|
|
return 4;
|
|
}
|
|
unsafe { dc_ws_state(id) }
|
|
}
|
|
|
|
pub fn ws_send(id: i32, text: &str) -> bool {
|
|
if id <= 0 {
|
|
return false;
|
|
}
|
|
unsafe { dc_ws_send(id, text.as_ptr(), text.len() as u32) != 0 }
|
|
}
|
|
|
|
pub fn ws_recv(id: i32) -> Option<String> {
|
|
if id <= 0 {
|
|
return None;
|
|
}
|
|
unsafe {
|
|
let n = dc_ws_take(id);
|
|
if n <= 0 {
|
|
return None;
|
|
}
|
|
let mut buf = vec![0u8; n as usize];
|
|
dc_storage_take(buf.as_mut_ptr());
|
|
String::from_utf8(buf).ok()
|
|
}
|
|
}
|
|
|
|
pub fn ws_close(id: i32) {
|
|
if id > 0 {
|
|
unsafe { dc_ws_close(id) }
|
|
}
|
|
}
|
|
|
|
pub fn renderer() -> String {
|
|
unsafe {
|
|
let n = dc_renderer();
|
|
if n < 0 {
|
|
return String::new();
|
|
}
|
|
let mut buf = vec![0u8; n as usize];
|
|
dc_storage_take(buf.as_mut_ptr());
|
|
String::from_utf8(buf).unwrap_or_default()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
mod imp {
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
thread_local! {
|
|
static STORE: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
|
|
}
|
|
pub fn storage_get(key: &str) -> Option<String> {
|
|
STORE.with(|s| s.borrow().get(key).cloned())
|
|
}
|
|
pub fn prompt(_title: &str, _default: &str) -> Option<String> {
|
|
None
|
|
}
|
|
pub fn coarse_pointer() -> bool {
|
|
false
|
|
}
|
|
pub fn open_url(_url: &str) {}
|
|
pub fn set_token(_token: &str) {}
|
|
pub fn fetch_download(_url: &str, _name: &str) {}
|
|
pub fn fetch_download_state() -> i32 {
|
|
0
|
|
}
|
|
pub fn storage_set(key: &str, val: &str) {
|
|
STORE.with(|s| s.borrow_mut().insert(key.to_string(), val.to_string()));
|
|
}
|
|
pub fn cache_on_leave(key: &str, val: &str) {
|
|
storage_set(key, val);
|
|
}
|
|
pub fn download(name: &str, data: &[u8]) {
|
|
let _ = std::fs::write(name, data);
|
|
}
|
|
pub fn open_file() {}
|
|
pub fn take_file() -> Option<Vec<u8>> {
|
|
None
|
|
}
|
|
// no savegame service in native builds: every request fails like an unreachable server
|
|
pub fn http(_method: &str, _url: &str, _body: &[u8]) -> u32 {
|
|
0
|
|
}
|
|
pub fn http_poll(_id: u32) -> Option<super::HttpResult> {
|
|
Some(super::HttpResult { status: 0, body: Vec::new() })
|
|
}
|
|
// no WebSocket in native builds: multi-user matches need the browser build
|
|
pub fn ws_connect(_url: &str) -> i32 {
|
|
0
|
|
}
|
|
pub fn ws_state(id: i32) -> i32 {
|
|
if id <= 0 {
|
|
0
|
|
} else {
|
|
4
|
|
}
|
|
}
|
|
pub fn ws_send(_id: i32, _text: &str) -> bool {
|
|
false
|
|
}
|
|
pub fn ws_recv(_id: i32) -> Option<String> {
|
|
None
|
|
}
|
|
pub fn ws_close(_id: i32) {}
|
|
|
|
pub fn renderer() -> String {
|
|
#[cfg(target_vendor = "apple")]
|
|
{
|
|
use macroquad::miniquad::conf::AppleGfxApi;
|
|
return match macroquad::miniquad::window::apple_gfx_api() {
|
|
AppleGfxApi::Metal => "Metal".into(),
|
|
AppleGfxApi::OpenGl => "OpenGL".into(),
|
|
};
|
|
}
|
|
#[cfg(not(target_vendor = "apple"))]
|
|
{
|
|
return "OpenGL".into();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub use imp::*;
|
|
|
|
// Lets the JS plugin loader know this crate uses the "dungeoncraft" plugin
|
|
#[no_mangle]
|
|
pub extern "C" fn dungeoncraft_crate_version() -> u32 {
|
|
1
|
|
}
|