engine, ecs: ticking
parent
d76ccd41f7
commit
d61c1c6a77
|
@ -15,6 +15,7 @@ rust_binary(
|
|||
srcs = [
|
||||
"src/main.rs",
|
||||
|
||||
"src/globals.rs",
|
||||
"src/input.rs",
|
||||
"src/scripting.rs",
|
||||
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
sent = {}
|
||||
sent.newindex = function(t, k, v)
|
||||
return __sent_components_newindex(t, k, v)
|
||||
end
|
||||
sent.index = function(t, k)
|
||||
return __sent_components_index(t, k)
|
||||
end
|
||||
sent.register = function (cfg)
|
||||
if cfg.name == nil then
|
||||
error("sent.register: needs name")
|
||||
|
|
|
@ -5,10 +5,19 @@ local cube_material = rm.get_material("test-128px")
|
|||
local Cube = {}
|
||||
|
||||
function Cube:init(x, y, z)
|
||||
self.name = string.format("%d %d %d", x, y, z)
|
||||
self.components.Transform = components.Transform.new(x, y, z)
|
||||
end
|
||||
|
||||
function Cube:tick()
|
||||
local xyzw = self.components.Transform:xyzw();
|
||||
local x = xyzw[1];
|
||||
local y = xyzw[2];
|
||||
local dist = math.sqrt(x*x + y*y)
|
||||
local z = math.sin(time*2+dist)*math.max(10-dist, 0)/10
|
||||
local new = components.Transform.new(x, y, z);
|
||||
--print(self.name, x, y, z, new:xyzw()[1], new:xyzw()[2], new:xyzw()[3])
|
||||
self.components.Transform = new
|
||||
end
|
||||
|
||||
sent.register({
|
||||
|
@ -20,14 +29,9 @@ sent.register({
|
|||
},
|
||||
})
|
||||
|
||||
for x=-2,2 do
|
||||
for y=-2,2 do
|
||||
for z=-2,2 do
|
||||
--if z > -2 and z < 2 and x > -2 and x < 2 and y > 0 then
|
||||
if x <= 0 and y <= 0 and z <= 0 then
|
||||
else
|
||||
Cube.new(x, y, z)
|
||||
end
|
||||
end
|
||||
local z = 0
|
||||
for x=-8,8 do
|
||||
for y=-8,8 do
|
||||
Cube.new(x, y, z)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
use std::time;
|
||||
use std::time::Instant;
|
||||
|
||||
pub struct Time {
|
||||
start: time::Instant,
|
||||
now: time::Instant,
|
||||
}
|
||||
impl ecs::Global for Time {}
|
||||
|
||||
impl Time {
|
||||
pub fn instant(&self) -> f32 {
|
||||
let instant_ns = self.now.duration_since(self.start).as_nanos() as u64;
|
||||
let instant = ((instant_ns/1000) as f32) / 1_000_000.0;
|
||||
instant
|
||||
}
|
||||
pub fn update(&mut self) {
|
||||
self.now = time::Instant::now();
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
let now = time::Instant::now();
|
||||
Self {
|
||||
start: now,
|
||||
now: now,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,10 +17,10 @@
|
|||
use log;
|
||||
use env_logger;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
|
||||
use cgmath as cgm;
|
||||
|
||||
pub mod globals;
|
||||
pub mod input;
|
||||
mod render;
|
||||
mod util;
|
||||
|
@ -34,20 +34,6 @@ use render::material::{Texture, PBRMaterialBuilder};
|
|||
use render::{Light, Material, Mesh, Transform, Renderable};
|
||||
use physics::color;
|
||||
|
||||
struct Time {
|
||||
start: time::Instant,
|
||||
now: time::Instant,
|
||||
}
|
||||
impl ecs::Global for Time {}
|
||||
|
||||
impl Time {
|
||||
pub fn instant(&self) -> f32 {
|
||||
let instant_ns = self.now.duration_since(self.start).as_nanos() as u64;
|
||||
let instant = ((instant_ns/1000) as f32) / 1_000_000.0;
|
||||
instant
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct Main {
|
||||
light1: ecs::EntityID,
|
||||
|
@ -156,7 +142,7 @@ impl Main {
|
|||
#[derive(Access)]
|
||||
struct MainData<'a> {
|
||||
scene_info: ecs::ReadWriteGlobal<'a, render::SceneInfo>,
|
||||
time: ecs::ReadGlobal<'a, Time>,
|
||||
time: ecs::ReadGlobal<'a, globals::Time>,
|
||||
input: ecs::ReadGlobal<'a, input::Input>,
|
||||
transforms: ecs::ReadWriteComponent<'a, Transform>,
|
||||
}
|
||||
|
@ -220,15 +206,11 @@ fn main() {
|
|||
p.add_system(context);
|
||||
p.add_system(renderer);
|
||||
|
||||
let start = time::Instant::now();
|
||||
world.set_global(Time {
|
||||
start,
|
||||
now: start,
|
||||
});
|
||||
world.set_global(globals::Time::new());
|
||||
world.set_global(input::Input::new());
|
||||
loop {
|
||||
world.queue_drain();
|
||||
world.global_mut::<Time>().get().now = time::Instant::now();
|
||||
world.global_mut::<globals::Time>().get().update();
|
||||
|
||||
p.run(&world);
|
||||
let status = world.global::<render::Status>().get();
|
||||
|
|
|
@ -99,7 +99,7 @@ impl mlua::UserData for Transform {
|
|||
methods.add_method("xyzw", |_, this, _: ()| {
|
||||
// TODO(q3k): lua wrappers for cgmath
|
||||
let xyzw = this.xyzw();
|
||||
Ok(vec![xyzw.z, xyzw.y, xyzw.z, xyzw.w])
|
||||
Ok(vec![xyzw.x, xyzw.y, xyzw.z, xyzw.w])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::sync::{Arc, Mutex, atomic};
|
||||
use std::sync::{Arc, Mutex, RwLock, atomic};
|
||||
use std::io::Read;
|
||||
|
||||
use crate::render;
|
||||
use crate::util;
|
||||
use crate::globals::Time;
|
||||
|
||||
use mlua::prelude::LuaError::RuntimeError;
|
||||
use mlua::ToLua;
|
||||
|
@ -117,11 +118,11 @@ impl ScriptedEntity {
|
|||
let componentsmeta = lua.create_table()?;
|
||||
componentsmeta.set(
|
||||
"__index",
|
||||
lua.globals().get::<_, mlua::Function>("__sent_components_index")?,
|
||||
lua.globals().get::<_, mlua::Table>("sent")?.get::<_, mlua::Function>("index")?,
|
||||
)?;
|
||||
componentsmeta.set(
|
||||
"__newindex",
|
||||
lua.globals().get::<_, mlua::Function>("__sent_components_newindex")?,
|
||||
lua.globals().get::<_, mlua::Table>("sent")?.get::<_, mlua::Function>("newindex")?,
|
||||
)?;
|
||||
components.set_metatable(Some(componentsmeta));
|
||||
components.raw_set("__sent_id", ScriptedEntityID {
|
||||
|
@ -142,7 +143,7 @@ pub struct WorldContext {
|
|||
// TODO(q3k): this leaks memory, right?
|
||||
lua: &'static mlua::Lua,
|
||||
classes: Arc<Mutex<BTreeMap<String, ScriptedEntityClass>>>,
|
||||
instances: Arc<Mutex<InstanceData>>,
|
||||
instances: Arc<RwLock<InstanceData>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -198,7 +199,7 @@ impl WorldContext {
|
|||
}).unwrap()).unwrap();
|
||||
|
||||
let classes = Arc::new(Mutex::new(BTreeMap::new()));
|
||||
let instances = Arc::new(Mutex::new(InstanceData {
|
||||
let instances = Arc::new(RwLock::new(InstanceData {
|
||||
ecs: BTreeMap::new(),
|
||||
internal_to_ecs: BTreeMap::new(),
|
||||
queue: Vec::new(),
|
||||
|
@ -290,6 +291,21 @@ impl WorldContext {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn scope_time<'a, 'world, 'lua, 'scope>(
|
||||
&'a self,
|
||||
scope: &'a mlua::Scope<'lua, 'scope>,
|
||||
world: &'world ecs::World,
|
||||
) -> mlua::Result<()>
|
||||
where
|
||||
'world: 'scope,
|
||||
'scope: 'a,
|
||||
{
|
||||
let globals = self.lua.globals();
|
||||
let time = world.global::<Time>();
|
||||
globals.set("time", time.get().instant());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn scope_sent<'a, 'world, 'lua, 'scope>(
|
||||
&'a self,
|
||||
scope: &'a mlua::Scope<'lua, 'scope>,
|
||||
|
@ -349,7 +365,7 @@ impl WorldContext {
|
|||
let seid: ScriptedEntityID = table.raw_get("__sent_id")?;
|
||||
|
||||
let classes = classes.lock().unwrap();
|
||||
let instances = instances.lock().unwrap();
|
||||
let instances = instances.read().unwrap();
|
||||
|
||||
// Get ScriptedEntity that the user requested this component for.
|
||||
let se = match instances.get(seid.internal_id) {
|
||||
|
@ -362,28 +378,29 @@ impl WorldContext {
|
|||
return Err(RuntimeError(format!("unknown entity {}", seid.internal_id)));
|
||||
}
|
||||
};
|
||||
|
||||
// Now retrieve the ScriptedEntityClass, which contains information about the
|
||||
// components that this entity has.
|
||||
let sec = match classes.get(&se.class_name) {
|
||||
Some(sec) => sec,
|
||||
None => {
|
||||
log::warn!("Lua code requested components for entity {} with unknown class {}", seid.internal_id, se.class_name);
|
||||
return Err(RuntimeError(format!("unknown class {}", se.class_name)));
|
||||
}
|
||||
};
|
||||
|
||||
// And retrieve the component.
|
||||
let component: &Box<dyn ecs::Component> = match sec.components.get(&key) {
|
||||
Some(c) => c,
|
||||
None => return Ok(mlua::Value::Nil),
|
||||
};
|
||||
|
||||
let ud: mlua::Value = match component.lua_userdata(lua) {
|
||||
Some(ud) => ud,
|
||||
None => return Err(RuntimeError(format!("unimplemented lua_userdata"))),
|
||||
};
|
||||
Ok(ud)
|
||||
match &se.status {
|
||||
ScriptedEntityStatus::QueuedForCreation(c) => match c.get(&key) {
|
||||
Some(component) => component.clone().lua_userdata(&lua).ok_or(RuntimeError(format!("lua_userdata not implemented"))),
|
||||
None => Err(RuntimeError(format!("unknown component {} in queued entity", key))),
|
||||
},
|
||||
ScriptedEntityStatus::Exists(ecsid) => match classes.get(&se.class_name) {
|
||||
Some(sec) => match sec.components.get(&key) {
|
||||
Some(component) => {
|
||||
let cid = component.id();
|
||||
let component = match world.component_get_dyn_cloned(*ecsid, cid) {
|
||||
Some(component) => component,
|
||||
None => {
|
||||
return Err(RuntimeError(format!("unknown component {:?} in ecs entity {}", cid, ecsid)));
|
||||
},
|
||||
};
|
||||
component.lua_userdata(&lua).ok_or(RuntimeError(format!("lua_userdata not implemented")))
|
||||
},
|
||||
None => Err(RuntimeError(format!("unknown component {} in class", key))),
|
||||
},
|
||||
None => Err(RuntimeError(format!("unknown class {}", se.class_name))),
|
||||
},
|
||||
ScriptedEntityStatus::FailedToCreate => return Err(RuntimeError(format!("cannot get component on failed entity"))),
|
||||
}
|
||||
})?)?;
|
||||
}
|
||||
{
|
||||
|
@ -399,7 +416,7 @@ impl WorldContext {
|
|||
let seid: ScriptedEntityID = table.raw_get("__sent_id")?;
|
||||
|
||||
let classes = classes.lock().unwrap();
|
||||
let mut instances = instances.lock().unwrap();
|
||||
let mut instances = instances.write().unwrap();
|
||||
|
||||
let se = match instances.get_mut(seid.internal_id) {
|
||||
Some(se) => se,
|
||||
|
@ -440,7 +457,6 @@ impl WorldContext {
|
|||
Some(cv) => cv,
|
||||
None => return Err(RuntimeError(format!("unimplemented lua_fromuserdata"))),
|
||||
};
|
||||
log::info!("component_value: {:?}", component_value);
|
||||
world.component_set_dyn(ecsid, component_value);
|
||||
|
||||
Ok(mlua::Value::Nil)
|
||||
|
@ -472,7 +488,7 @@ impl WorldContext {
|
|||
);
|
||||
sent.set_metatable(lua, world)?;
|
||||
|
||||
instances.lock().unwrap().queue.push(sent);
|
||||
instances.write().unwrap().queue.push(sent);
|
||||
|
||||
Ok(table)
|
||||
})?)?;
|
||||
|
@ -495,32 +511,89 @@ impl WorldContext {
|
|||
}
|
||||
}
|
||||
|
||||
impl <'system> ecs::System<'system> for WorldContext {
|
||||
impl <'system, 'lua, 'scope> ecs::System<'system> for WorldContext
|
||||
where
|
||||
'lua: 'scope,
|
||||
'system: 'scope,
|
||||
{
|
||||
type SystemData = ecs::ReadWriteAll<'system>;
|
||||
fn run(&mut self, sd: Self::SystemData) {
|
||||
let mut instances = self.instances.lock().unwrap();
|
||||
let classes = self.classes.lock().unwrap();
|
||||
{
|
||||
let instances = self.instances.clone();
|
||||
let classes = self.classes.clone();
|
||||
let mut instances = instances.write().unwrap();
|
||||
let classes = classes.lock().unwrap();
|
||||
// Lazily create enqueued entities.
|
||||
if instances.queue.len() > 0 {
|
||||
let queue = std::mem::replace(&mut instances.queue, Vec::new());
|
||||
|
||||
// Lazily create enqueued entities.
|
||||
if instances.queue.len() > 0 {
|
||||
let queue = std::mem::replace(&mut instances.queue, Vec::new());
|
||||
|
||||
for mut el in queue.into_iter() {
|
||||
match el.status {
|
||||
ScriptedEntityStatus::QueuedForCreation(components) => {
|
||||
let mut entity = sd.new_entity_lazy();
|
||||
for (_, component) in components.into_iter() {
|
||||
entity = entity.with_dyn(component);
|
||||
for mut el in queue.into_iter() {
|
||||
match el.status {
|
||||
ScriptedEntityStatus::QueuedForCreation(components) => {
|
||||
let mut entity = sd.new_entity_lazy();
|
||||
for (_, component) in components.into_iter() {
|
||||
entity = entity.with_dyn(component);
|
||||
}
|
||||
let ecsid = entity.build(&sd);
|
||||
el.status = ScriptedEntityStatus::Exists(ecsid);
|
||||
log::debug!("Created sent of type {} with ECS ID {} and internal ID {}", el.class_name, ecsid, el.internal_id);
|
||||
instances.internal_to_ecs.insert(el.internal_id, ecsid);
|
||||
instances.ecs.insert(ecsid, el);
|
||||
}
|
||||
let ecsid = entity.build(&sd);
|
||||
el.status = ScriptedEntityStatus::Exists(ecsid);
|
||||
log::debug!("Created sent of type {} with ECS ID {} and internal ID {}", el.class_name, ecsid, el.internal_id);
|
||||
instances.internal_to_ecs.insert(el.internal_id, ecsid);
|
||||
instances.ecs.insert(ecsid, el);
|
||||
o => panic!("ScriptedEntity in queue with unexpected status {:?}", o),
|
||||
}
|
||||
o => panic!("ScriptedEntity in queue with unexpected status {:?}", o),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let world: &'system ecs::World = sd.world();
|
||||
{
|
||||
let instances = self.instances.clone();
|
||||
// Run tick functions in all entities.
|
||||
self.lua.scope::<'lua, 'scope>(move |scope| {
|
||||
self.global_set_components(world)?;
|
||||
self.scope_sent(scope, world)?;
|
||||
self.scope_resourcemanager(scope, world)?;
|
||||
self.scope_time(scope, world)?;
|
||||
// Copy out functions refs to release lock.
|
||||
let mut to_run: Vec<(ecs::EntityID, mlua::Function)> = vec![];
|
||||
{
|
||||
let instances = instances.read().unwrap();
|
||||
for (ecsid, sent) in instances.ecs.iter() {
|
||||
let table: mlua::Table = match self.lua.registry_value(&sent.table) {
|
||||
Ok(table) => table,
|
||||
Err(err) => {
|
||||
log::warn!("Entity {}/{} tick failed: getting tick failed: {:?}", ecsid, sent.internal_id, err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let tick: mlua::Function = match table.get("tick") {
|
||||
Ok(tick) => tick,
|
||||
Err(err) => {
|
||||
log::warn!("Entity {}/{} tick failed: getting tick function failed: {:?}", ecsid, sent.internal_id, err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let cb = match tick.bind(table) {
|
||||
Ok(cb) => cb,
|
||||
Err(err) => {
|
||||
log::warn!("Entity {}/{} tick failed: could not bind: {:?}", ecsid, sent.internal_id, err);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
to_run.push((*ecsid, cb));
|
||||
}
|
||||
}
|
||||
for (ecsid, cb) in to_run.into_iter() {
|
||||
match cb.call::<mlua::Value, mlua::Value>(mlua::Value::Nil) {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
log::warn!("Entity {} tick failed: {:?}", ecsid, err);
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,28 @@ impl ComponentMap {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_dyn<'a>(&'a self, e: entity::ID) -> Result<RefDyn<'a>, AccessError> {
|
||||
unsafe {
|
||||
match borrow::Ref::new(&self.borrow) {
|
||||
None => Err(AccessError("already borrowed mutably".to_string())),
|
||||
Some(b) => {
|
||||
let map = &*self.value.get();
|
||||
match map.get(&e) {
|
||||
None => Err(AccessError("no such entity".to_string())),
|
||||
Some(component) => {
|
||||
let component = component.as_ref();
|
||||
let val = component as *const (dyn component::Component);
|
||||
Ok(RefDyn {
|
||||
val,
|
||||
borrow: Some(b),
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get<'a, T: component::Component>(&'a self, e: entity::ID) -> Result<Ref<'a, T>, AccessError> {
|
||||
match borrow::Ref::new(&self.borrow) {
|
||||
None => Err(AccessError("already borrowed mutably".to_string())),
|
||||
|
@ -107,6 +129,27 @@ impl ComponentMap {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct RefDyn<'a> {
|
||||
val: *const (dyn component::Component),
|
||||
borrow: Option<borrow::Ref<'a>>,
|
||||
}
|
||||
|
||||
impl <'a> Drop for RefDyn<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.borrow = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a> Deref for RefDyn<'a> {
|
||||
type Target = dyn component::Component;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe {
|
||||
&(*self.val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Ref<'a, T: component::Component> {
|
||||
val: *const T,
|
||||
borrow: Option<borrow::Ref<'a>>,
|
||||
|
|
|
@ -140,6 +140,12 @@ pub struct ReadWriteAll<'a> {
|
|||
world: &'a World,
|
||||
}
|
||||
|
||||
impl<'a> ReadWriteAll<'a> {
|
||||
pub fn world(self) -> &'a World {
|
||||
self.world
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ReadWriteAll<'a> {}
|
||||
|
||||
impl<'a> std::ops::Deref for ReadWriteAll<'a> {
|
||||
|
@ -263,6 +269,28 @@ impl World {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn component_get_dyn_cloned<'a>(
|
||||
&'a self,
|
||||
e: entity::ID,
|
||||
cid: component::ID,
|
||||
) -> Option<Box<dyn component::Component>> {
|
||||
let map = self.components.get(&cid)?;
|
||||
match map.get_dyn(e) {
|
||||
Ok(val) => Some(val.clone_dyn()),
|
||||
Err(err) => {
|
||||
// Attempt to get from queue.
|
||||
let cq = self.component_queue.borrow();
|
||||
for (qcid, qc, qe) in cq.iter() {
|
||||
if qe.id() == e && *qcid == cid {
|
||||
return Some(qc.clone_dyn());
|
||||
}
|
||||
}
|
||||
// TODO(q3k): better error handling
|
||||
panic!("get_dyn({:?}): not in queue and ecs said: {:?}", e, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn component_set_dyn<'a>(
|
||||
&'a self,
|
||||
e: entity::ID,
|
||||
|
|
Loading…
Reference in New Issue