win = require("window") log = require("log") world = require("world") Component = require("ecs.component") GraphicsComponent = require("ecs.graphics") NetworkedComponent = require("ecs.networked") PredictedComponent = require("ecs.predicted") ScriptComponent = require("ecs.script") CharacterControllerComponent = require("ecs.char_controller") shader_world = require("shaders.world") --win.scene:append(shader_world) --pp = am.postprocess({ --clear_color: vec4(0.2, 0.2, 0.3, 1), --depth_buffer: true, ----stencil_buffer: true, --width: win.width, --height: win.height, --}) pp = am.group! pp\append(shader_world.node) GraphicsComponent.node = pp ScriptComponent.node = pp PredictedComponent.node = pp NetworkedComponent.node = pp -- Base entity of our ECS class Entity id: 1 new: (id, components) => -- [id], [components] ent_reg = world.domain == "client" and world.level.entities or world.level_sync.ents id_cur = id or #ent_reg + 1 @created_at = debug.traceback! assert(id_cur < 10, "Got more then 10 entities") while ent_reg[id_cur] != nil id_cur += 1 log.debug("Want to create an entity with id" .. id_cur .. ", registry is:" .. tostring(ent_reg) .. "\n" .. debug.traceback(), {"ecs"}) assert(ent_reg[id_cur] == nil, "Attempted to create entity with the same id as one that already exists: " .. tostring(id_cur) .. ", entities were:" .. tostring(ent_reg)) log.info("Creating entity " .. id_cur .. " domain is: " .. world.domain, {"ecs"}) ent_reg[id_cur] = @ @reg = ent_reg @id = id_cur -- Bookkeeping for O(1) access for components @c_by_type = {} @c_by_name = {} --Entity is responsible for the component -> entity link @components = components or {} for name, component in pairs(@components) component.entity = @ @c_by_type[component.__class] = @c_by_type[component.__class] or {} @c_by_type[component.__class][name] = component for name, component in pairs(@components) assert(component.join, "Component " .. name .. " does not have a join method.") component\join(@) component\post_join(@) add: (component, cid) => assert(component, "Failed to find component:" .. tostring(component)) assert(type(component) == "table", "Component must be a table") assert(component.__class, "Component must have a class") component.entity = @ component\join(@, cid) component\post_join(@, cid) if cid == nil cid = #@components + 1 while @components[cid] cid += 1 assert(@components[cid] == nil, "Already had a component with id" .. tostring(cid)) @components[cid] = component @c_by_type[component.__class] = @c_by_type[component.__class] or {} @c_by_type[component.__class][cid] = component assert(@c_by_name[component.name] == nil, "Duplicate components named" .. component.name) @c_by_name[component.name] = component @ remove: (cid) => component = @components[cid] component.entity = nil component\leave(@) component @components[cid] = nil @c_by_type[component.__class][cid] = nil @c_by_name[component.name] = nil destroy: () => for name, component in pairs(@components) @remove(name) @reg[@id] = nil get: (cid) => @components[cid] serialize: () => components = {} for i, component in pairs(@components) component_name = component.__class.__name if not component.serialize log.warn("Component:" .. component_name .. " does not have a .serialize()", {"ecs"}) continue component_data = component\serialize! components[i] = string.format( "%d\0%s%d\0%s", #component_name, component_name, #component_data, component_data ) return table.concat(components, "\0") --@deserialize: (data) => --error("Deserialize called") --log.info("Deserializing entity from data of length " .. tostring(data and #data or 0), {"ecs"}) --ent = @! ---- Empty payload -> empty entity --if not data or #data == 0 --return ent ---- Map serialized component type names back to their classes --component_types = { --[GraphicsComponent.__name]: GraphicsComponent --[NetworkedComponent.__name]: NetworkedComponent --[PredictedComponent.__name]: PredictedComponent --[ScriptComponent.__name]: ScriptComponent --[CharacterControllerComponent.__name]: CharacterControllerComponent --[PhysicsComponent.__name]: PhysicsComponent --} --pointer = 1 --len = #data --while pointer <= len ---- Read component name length (digits up to first NUL) --name_len_end = string.find(data, "\0", pointer) --if not name_len_end --break --name_len_str = string.sub(data, pointer, name_len_end - 1) --name_len = tonumber(name_len_str) --if not name_len or name_len <= 0 --break --pointer = name_len_end + 1 ---- Read component type name --component_name = string.sub(data, pointer, pointer + name_len - 1) --pointer += name_len ---- Read component data length (digits up to next NUL) --data_len_end = string.find(data, "\0", pointer) --if not data_len_end --break --data_len_str = string.sub(data, pointer, data_len_end - 1) --data_len = tonumber(data_len_str) --if not data_len or data_len < 0 --break --pointer = data_len_end + 1 ---- Read component payload --component_data = string.sub(data, pointer, pointer + data_len - 1) --pointer += data_len ---- Skip inter-component separator if present --if string.sub(data, pointer, pointer) == "\0" --pointer += 1 --ctype = component_types[component_name] --if not ctype --log.warn("Unknown component type during deserialize: " .. tostring(component_name), {"ecs"}) --continue --if not ctype.deserialize --log.warn("Component type " .. tostring(component_name) .. " does not implement .deserialize()", {"ecs"}) --continue ---- Let the component class decide how to interpret its payload --component = ctype.deserialize(component_data) --if component --ent\add(component) --return ent __tostring: () => components_str = tostring(@components) return string.format("%s : {\n%s\n}\t%q",@@__name, components_str\gsub("^","\t"), @created_at) class PhysicsComponent extends Component new: (name, properties) => assert(properties and properties.shape, "Failed to find a shape for physics component") super(name, properties) { :Component :Entity :NetworkedComponent :PredictedComponent :GraphicsComponent :PhysicsComponent :ScriptComponent :CharacterControllerComponent node: pp }