use std::ffi::c_void;
use bevy_app::{App, Plugin};
use bevy_ecs::{
prelude::{EventReader, EventWriter},
system::ResMut,
};
use bevy_wasm_shared::prelude::*;
use serde::{de::DeserializeOwned, Serialize};
use crate::{
ecs::extern_res::ExternResources,
error,
events::{get_next_event, send_event},
ffi::store_app,
info,
time::Time,
};
pub trait Message: Send + Sync + Serialize + DeserializeOwned + 'static {}
impl<T> Message for T where T: Send + Sync + Serialize + DeserializeOwned + 'static {}
pub struct FFIPlugin<In: Message, Out: Message> {
protocol_version: Version,
protocol_version_checker: Box<dyn Fn(Version, Version) -> bool + Send + Sync + 'static>,
_in: std::marker::PhantomData<In>,
_out: std::marker::PhantomData<Out>,
}
impl<In: Message, Out: Message> FFIPlugin<In, Out> {
pub fn new(protocol_version: Version) -> Self {
info!(
"Starting mod with protocol version {}.{}.{}",
protocol_version.major, protocol_version.minor, protocol_version.patch
);
Self {
protocol_version,
protocol_version_checker: Box::new(|host_version, mod_version| {
host_version.name_hash == mod_version.name_hash
&& host_version.major == mod_version.major
}),
_in: std::marker::PhantomData,
_out: std::marker::PhantomData,
}
}
pub fn with_version_checker<F>(self, checker: F) -> Self
where
F: Fn(Version, Version) -> bool + Send + Sync + 'static,
{
Self {
protocol_version_checker: Box::new(checker),
..self
}
}
}
impl<In: Message, Out: Message> Plugin for FFIPlugin<In, Out> {
fn build(&self, app: &mut App) {
let host_version = unsafe { crate::ffi::get_protocol_version() };
let host_version = Version::from_u64(host_version);
if !(*self.protocol_version_checker)(self.protocol_version, host_version) {
error!(
"Protocol version incompatible! Host: {}.{}.{}, Mod: {}.{}.{}",
host_version.major,
host_version.minor,
host_version.patch,
self.protocol_version.major,
self.protocol_version.minor,
self.protocol_version.patch
);
return;
}
app.set_runner(app_runner)
.add_event::<In>()
.add_event::<Out>()
.insert_resource(Time::new())
.insert_resource(ExternResources::new())
.add_system(update_time)
.add_system(fetch_resources)
.add_system(event_listener::<In>)
.add_system(event_sender::<Out>);
}
}
fn fetch_resources(mut resources: ResMut<ExternResources>) {
resources.fetch_all();
}
fn event_listener<M: Message>(mut events: EventWriter<M>) {
while let Some(event) = get_next_event() {
events.send(event);
}
}
fn event_sender<M: Message>(mut events: EventReader<M>) {
for event in events.iter() {
send_event(event);
}
}
fn app_runner(app: App) {
let app = Box::new(app);
let app_ptr = Box::into_raw(app);
let app_ptr = app_ptr as *const c_void;
unsafe { store_app(app_ptr) };
}
fn update_time(mut time: ResMut<Time>) {
time.update();
}