engine, ecs: ticking

master
q3k 2021-04-08 17:56:11 +00:00
parent d76ccd41f7
commit d61c1c6a77
9 changed files with 245 additions and 82 deletions

View File

@ -15,6 +15,7 @@ rust_binary(
srcs = [
"src/main.rs",
"src/globals.rs",
"src/input.rs",
"src/scripting.rs",

View File

@ -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")

View File

@ -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

26
engine/src/globals.rs Normal file
View File

@ -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,
}
}
}

View File

@ -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();

View File

@ -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])
});
}
}

View File

@ -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(())
});
}
}
}

View File

@ -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>>,

View File

@ -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,