From e975c4b9f8c0a0278460c522ba48a628b7b60ed7 Mon Sep 17 00:00:00 2001 From: Jesse Rusak Date: Sat, 8 Feb 2020 14:07:39 -0500 Subject: [PATCH] WIP refactor API --- .gitignore | 3 +- README.md | 1 - server/Cargo.lock | 7 ++ server/Cargo.toml | 3 +- server/src/chat.rs | 2 +- server/src/main.rs | 5 +- server/src/object/actor.rs | 100 +++++++++++++++++++++++++ server/src/object/api.rs | 145 ++++++++++++++++++++++++++++++++++++ server/src/object/mod.rs | 2 + server/src/object_actor.rs | 149 ------------------------------------- server/src/world.rs | 2 +- 11 files changed, 263 insertions(+), 156 deletions(-) create mode 100644 server/src/object/actor.rs create mode 100644 server/src/object/api.rs create mode 100644 server/src/object/mod.rs delete mode 100644 server/src/object_actor.rs diff --git a/.gitignore b/.gitignore index 69a014c..a5c07e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .* ~* -world.json -world.bak.json +/server/state diff --git a/README.md b/README.md index 047bfed..eb38de6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ Note that the config and dockerfiles are aimed at production use, not developmen # TODO -* sessions so websocket reconnect re-connects to user * chat history * passwords * ping/pong diff --git a/server/Cargo.lock b/server/Cargo.lock index f252fbc..e4f1c02 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -1018,6 +1018,7 @@ dependencies = [ "multimap 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "rlua 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1208,6 +1209,11 @@ name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "scopeguard" version = "1.0.0" @@ -1668,6 +1674,7 @@ dependencies = [ "checksum rlua 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "25fa5b2c667bae0b6218361e96d365e414fe4a0fa80f476b9631aa2dea2c6881" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" diff --git a/server/Cargo.toml b/server/Cargo.toml index 045900f..1c8925c 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -20,4 +20,5 @@ uuid = { version = "0.8.1", features = ["v4"] } multimap = "0.8.0" regex = "1.3.4" ctrlc = { version = "3.1.3", features = ["termination"] } -lazy_static = "1.4.0" \ No newline at end of file +lazy_static = "1.4.0" +scoped-tls = "1.0.0" \ No newline at end of file diff --git a/server/src/chat.rs b/server/src/chat.rs index ebe5959..3766600 100644 --- a/server/src/chat.rs +++ b/server/src/chat.rs @@ -1,5 +1,5 @@ use crate::lua::SerializableValue; -use crate::object_actor::ObjectMessage; +use crate::object::actor::ObjectMessage; use crate::world::{Id, WorldRef}; use actix::{Actor, AsyncContext, Handler, Message, StreamHandler}; use actix_web::web; diff --git a/server/src/main.rs b/server/src/main.rs index b56afad..6171afb 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,6 +1,6 @@ mod chat; mod lua; -mod object_actor; +mod object; mod util; mod world; @@ -18,6 +18,9 @@ use std::env; use std::fs::{copy, rename, File}; use std::path::Path; +#[macro_use] +extern crate scoped_tls; + async fn index() -> impl Responder { HttpResponse::Ok().body("Hello world!") } diff --git a/server/src/object/actor.rs b/server/src/object/actor.rs new file mode 100644 index 0000000..2d4193d --- /dev/null +++ b/server/src/object/actor.rs @@ -0,0 +1,100 @@ +use crate::lua::LuaHost; +use crate::lua::SerializableValue; +use crate::object; +use crate::world::*; + +use actix::{Actor, Context, Handler, Message}; +use rlua; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +pub struct ObjectActor { + pub(super) id: Id, + pub(super) world: WorldRef, + pub(super) lua_state: rlua::Lua, + pub(super) state: ObjectActorState, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ObjectActorState { + persistent_state: HashMap, +} + +impl ObjectActor { + pub fn new( + id: Id, + world_ref: WorldRef, + state: Option, + lua_host: &LuaHost, + ) -> ObjectActor { + ObjectActor { + id: id, + world: world_ref, + state: state.unwrap_or(ObjectActorState { + persistent_state: HashMap::new(), + }), + lua_state: lua_host.fresh_state().unwrap(), + } + } + + fn run_main(&mut self, msg: ObjectMessage) -> rlua::Result<()> { + // we hold a read lock on the world as a simple form of "transaction isolation" for now + // this is not useful right now but prevents us from accidentally writing to the world + // which could produce globally-visible effects while other objects are running. + let wf = self.world.clone(); + let id = self.id; + wf.read(|_w| { + object::api::with_api(self, |lua_ctx| { + let globals = lua_ctx.globals(); + let orisa: rlua::Table = globals.get("orisa")?; + orisa.set("self", id)?; + let main: rlua::Function = globals.get("main")?; + + main.call::<_, ()>((msg.immediate_sender, msg.name, msg.payload)) + }) + }) + } +} + +impl Actor for ObjectActor { + type Context = Context; + + fn started(&mut self, _ctx: &mut Self::Context) { + self + .lua_state + .context(|ctx| object::api::register_api(ctx)) + .unwrap(); + } +} + +impl Handler for ObjectActor { + type Result = (); + + fn handle(&mut self, msg: ObjectMessage, _ctx: &mut Self::Context) { + let _ = self + .run_main(msg) + .map_err(|err: rlua::Error| log::error!("Failed running payload: {:?}", err)); + } +} + +impl Handler for ObjectActor { + type Result = Option; + + fn handle(&mut self, _msg: FreezeMessage, _ctx: &mut Self::Context) -> Option { + Some(FreezeResponse { + id: self.id, + state: self.state.clone(), + }) + } +} + +#[derive(Debug)] +pub struct ObjectMessage { + pub immediate_sender: Id, + pub name: String, + pub payload: SerializableValue, +} + +impl Message for ObjectMessage { + type Result = (); +} diff --git a/server/src/object/api.rs b/server/src/object/api.rs new file mode 100644 index 0000000..b637ee6 --- /dev/null +++ b/server/src/object/api.rs @@ -0,0 +1,145 @@ +use crate::object::actor::ObjectActor; +use crate::world::{Id, World}; +use rlua; +use std::cell::RefCell; + +pub struct ObjectApiExecutionState<'a> { + actor: RefCell<&'a mut ObjectActor>, +} + +impl<'a> ObjectApiExecutionState<'a> { + fn new(actor: &mut ObjectActor) -> ObjectApiExecutionState { + ObjectApiExecutionState { + actor: RefCell::new(actor), + } + } + + fn with_state(body: F) -> T + where + F: FnOnce(&ObjectApiExecutionState) -> T, + { + EXECUTION_STATE.with(|s| body(s)) + } + + fn with_actor(body: F) -> T + where + F: FnOnce(&ObjectActor) -> T, + { + Self::with_state(|s| body(&s.actor.borrow())) + } + + fn with_world(body: F) -> T + where + F: FnOnce(&World) -> T, + { + Self::with_state(|s| s.actor.borrow().world.read(|w| body(w))) + } + + fn get_id() -> Id { + Self::with_actor(|a| a.id) + } +} + +scoped_thread_local! {static EXECUTION_STATE: ObjectApiExecutionState} + +// API + +mod api { + use crate::chat::{ChatRowContent, ToClientMessage}; + use crate::lua::*; + use crate::object::actor::ObjectMessage; + use crate::object::api::ObjectApiExecutionState as S; + use crate::world::Id; + pub fn get_children(_lua_ctx: rlua::Context, object_id: Id) -> rlua::Result> { + Ok(S::with_world(|w| { + w.children(object_id).collect::>() + })) + } + + pub fn send( + _lua_ctx: rlua::Context, + (object_id, name, payload): (Id, String, SerializableValue), + ) -> rlua::Result<()> { + Ok(S::with_world(|w| { + w.send_message( + object_id, + ObjectMessage { + immediate_sender: S::get_id(), + name: name, + payload: payload, + }, + ) + })) + } + + pub fn tell(_lua_ctx: rlua::Context, message: String) -> rlua::Result<()> { + Ok(S::with_world(|w| { + w.send_client_message( + S::get_id(), + ToClientMessage::Tell { + content: ChatRowContent::new(&message), + }, + ) + })) + } + + pub fn get_name(_lua_ctx: rlua::Context, id: Id) -> rlua::Result { + Ok(S::with_world(|w| w.username(id))) + } + + pub fn get_kind(_lua_ctx: rlua::Context, id: Id) -> rlua::Result { + Ok(S::with_world(|w| w.kind(id).0)) + } + + // orisa.set( + // "set_state", + // scope + // .create_function_mut( + // |_lua_ctx, (object_id, key, value): (Id, String, SerializableValue)| { + // if object_id != self.id { + // // Someday we might relax this given capabilities and probably containment (for concurrency) + // // Err("Can only set your own properties.") + // Ok(()) + // } else { + // self.state.persistent_state.insert(key, value); + // Ok(()) + // } + // }, + // ) + // .unwrap(), + // ); +} + +pub fn register_api(lua_ctx: rlua::Context) -> rlua::Result<()> { + let globals = lua_ctx.globals(); + let orisa = lua_ctx.create_table()?; + + orisa.set("get_children", lua_ctx.create_function(api::get_children)?)?; + orisa.set("send", lua_ctx.create_function(api::send)?)?; + orisa.set("tell", lua_ctx.create_function(api::tell)?)?; + orisa.set("get_name", lua_ctx.create_function(api::get_name)?)?; + orisa.set("get_kind", lua_ctx.create_function(api::get_kind)?)?; + + globals.set("orisa", orisa)?; + Ok(()) +} + +pub fn with_api<'a, F, T>(actor: &mut ObjectActor, body: F) -> T +where + F: FnOnce(rlua::Context) -> T, +{ + let state = ObjectApiExecutionState::new(actor); + + // This is a gross hack but is safe since the scoped thread local ensures + // this value only exists as long as this block. + EXECUTION_STATE.set(unsafe { make_static(&state) }, || { + ObjectApiExecutionState::with_actor(|actor| actor.lua_state.context(|lua_ctx| body(lua_ctx))) + }) +} + +unsafe fn make_static<'a>( + p: &'a ObjectApiExecutionState<'a>, +) -> &'static ObjectApiExecutionState<'static> { + use std::mem; + mem::transmute(p) +} diff --git a/server/src/object/mod.rs b/server/src/object/mod.rs new file mode 100644 index 0000000..b134a2b --- /dev/null +++ b/server/src/object/mod.rs @@ -0,0 +1,2 @@ +pub mod actor; +mod api; diff --git a/server/src/object_actor.rs b/server/src/object_actor.rs deleted file mode 100644 index 064e2ab..0000000 --- a/server/src/object_actor.rs +++ /dev/null @@ -1,149 +0,0 @@ -use crate::chat::{ChatRowContent, ToClientMessage}; -use crate::lua::LuaHost; -use crate::lua::SerializableValue; -use crate::world::*; -use actix::{Actor, Context, Handler, Message}; -use rlua; -use serde::{Deserialize, Serialize}; - -pub struct ObjectActor { - id: Id, - world: WorldRef, - lua_state: rlua::Lua, - state: ObjectActorState, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct ObjectActorState {} - -impl ObjectActor { - pub fn new( - id: Id, - world_ref: WorldRef, - state: Option, - lua_host: &LuaHost, - ) -> ObjectActor { - ObjectActor { - id: id, - world: world_ref, - state: state.unwrap_or(ObjectActorState {}), - lua_state: lua_host.fresh_state().unwrap(), - } - } -} - -impl Actor for ObjectActor { - type Context = Context; - - fn started(&mut self, _ctx: &mut Self::Context) { - self - .lua_state - .context::<_, rlua::Result<()>>(|lua_ctx| { - let globals = lua_ctx.globals(); - - let orisa = lua_ctx.create_table()?; - let wf = self.world.clone(); - orisa.set("id", self.id)?; - orisa.set( - "get_children", - lua_ctx.create_function(move |_lua_ctx, object_id: Id| { - Ok(wf.read(|w| w.children(object_id).collect::>())) - })?, - )?; - - let wf = self.world.clone(); - let id = self.id; - orisa.set( - "send", - lua_ctx.create_function( - move |_lua_ctx, (object_id, name, payload): (Id, String, SerializableValue)| { - Ok(wf.read(|w| { - w.send_message( - object_id, - ObjectMessage { - immediate_sender: id, - name: name, - payload: payload, - }, - ) - })) - }, - )?, - )?; - - let wf = self.world.clone(); - orisa.set( - "tell", - lua_ctx.create_function(move |_lua_ctx, message: String| { - Ok(wf.read(|w| { - w.send_client_message( - id, - ToClientMessage::Tell { - content: ChatRowContent::new(&message), - }, - ) - })) - })?, - )?; - - let wf = self.world.clone(); - orisa.set( - "name", - lua_ctx.create_function(move |_lua_ctx, id: Id| Ok(wf.read(|w| w.username(id))))?, - )?; - - globals.set("orisa", orisa)?; - Ok(()) - }) - .unwrap(); - } -} - -impl Handler for ObjectActor { - type Result = (); - - fn handle(&mut self, msg: ObjectMessage, _ctx: &mut Self::Context) { - let sender = msg.immediate_sender; - // we hold a read lock on the world as a simple form of "transaction isolation" for now - // this is not useful right now but prevents us from accidentally writing to the world - // which could produce globally-visible effects while other objects are running. - self.world.read(|w| { - let kind = w.kind(self.id); - let _ = self - .lua_state - .context(|lua_ctx| { - let globals = lua_ctx.globals(); - let orisa: rlua::Table = globals.get("orisa")?; - orisa.set("kind", kind.0)?; - orisa.set("sender", sender)?; - - let main: rlua::Function = globals.get("main")?; - main.call((msg.name, msg.payload))?; - Ok(()) - }) - .map_err(|err: rlua::Error| log::error!("Failed running payload: {:?}", err)); - }); - } -} - -impl Handler for ObjectActor { - type Result = Option; - - fn handle(&mut self, _msg: FreezeMessage, _ctx: &mut Self::Context) -> Option { - Some(FreezeResponse { - id: self.id, - state: self.state.clone(), - }) - } -} - -#[derive(Debug)] -pub struct ObjectMessage { - pub immediate_sender: Id, - pub name: String, - pub payload: SerializableValue, -} - -impl Message for ObjectMessage { - type Result = (); -} diff --git a/server/src/world.rs b/server/src/world.rs index 10fd559..40c819f 100644 --- a/server/src/world.rs +++ b/server/src/world.rs @@ -1,6 +1,6 @@ use crate::chat::{ChatSocket, ToClientMessage}; use crate::lua::LuaHost; -use crate::object_actor::*; +use crate::object::actor::*; use actix::{Actor, Addr, Arbiter, Message}; use futures::executor; use futures::stream::FuturesUnordered;