Lua API
This commit is contained in:
@@ -337,11 +337,60 @@ target_include_directories(editSceneEditor PRIVATE
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test: ActionDatabase Lua API
|
||||
# Tests: Lua API standalone tests
|
||||
# ---------------------------------------------------------------------------
|
||||
# Standalone test that verifies the ActionDatabase singleton and Lua API
|
||||
# work correctly. Does not require OGRE or Flecs - only Lua and the
|
||||
# core component types.
|
||||
# These standalone tests verify the Lua API functions work correctly.
|
||||
# They do not require OGRE or Flecs - only Lua and the core component types.
|
||||
|
||||
# Test: Entity Lua API
|
||||
add_executable(entity_lua_test
|
||||
tests/entity_lua_test.cpp
|
||||
tests/lua_test_stubs.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(entity_lua_test
|
||||
lua
|
||||
)
|
||||
|
||||
target_include_directories(entity_lua_test PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests
|
||||
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
|
||||
)
|
||||
|
||||
# Test: Component Lua API
|
||||
add_executable(component_lua_test
|
||||
tests/component_lua_test.cpp
|
||||
tests/lua_test_stubs.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(component_lua_test
|
||||
lua
|
||||
)
|
||||
|
||||
target_include_directories(component_lua_test PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests
|
||||
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
|
||||
)
|
||||
|
||||
# Test: Event Lua API
|
||||
add_executable(event_lua_test
|
||||
tests/event_lua_test.cpp
|
||||
tests/lua_test_stubs.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(event_lua_test
|
||||
lua
|
||||
)
|
||||
|
||||
target_include_directories(event_lua_test PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests
|
||||
${CMAKE_SOURCE_DIR}/src/lua/lua-5.4.8/src
|
||||
)
|
||||
|
||||
# Test: ActionDatabase Lua API
|
||||
add_executable(action_db_lua_test
|
||||
tests/action_db_lua_test.cpp
|
||||
components/ActionDatabase.cpp
|
||||
@@ -353,6 +402,7 @@ add_executable(action_db_lua_test
|
||||
|
||||
target_link_libraries(action_db_lua_test
|
||||
lua
|
||||
flecs::flecs_static
|
||||
)
|
||||
|
||||
target_include_directories(action_db_lua_test PRIVATE
|
||||
|
||||
711
src/features/editScene/lua-examples/component_example.lua
Normal file
711
src/features/editScene/lua-examples/component_example.lua
Normal file
@@ -0,0 +1,711 @@
|
||||
-- =============================================================================
|
||||
-- Component Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to add, remove, query, and manipulate ECS
|
||||
-- components from Lua using the ecs.* Lua API.
|
||||
--
|
||||
-- Components are data attached to entities. They define what an entity IS
|
||||
-- and what it CAN DO. For example, a Transform component gives an entity
|
||||
-- a position in the world, while a Renderable component makes it visible.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Checking for Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create an entity and check what components it has:
|
||||
local entity = ecs.create_entity()
|
||||
ecs.set_entity_name(entity, "TestObject")
|
||||
|
||||
-- Check if an entity has a specific component:
|
||||
if ecs.has_component(entity, "Transform") then
|
||||
print("Entity has Transform component")
|
||||
end
|
||||
|
||||
-- New entities typically have EditorMarker by default:
|
||||
if ecs.has_component(entity, "EditorMarker") then
|
||||
print("Entity has EditorMarker (default for new entities)")
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Adding and Removing Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Add a tag component (a component with no data):
|
||||
ecs.add_component(entity, "InWater")
|
||||
print("Added InWater tag")
|
||||
|
||||
-- Check it was added:
|
||||
if ecs.has_component(entity, "InWater") then
|
||||
print("Entity is now in water")
|
||||
end
|
||||
|
||||
-- Remove a tag component:
|
||||
ecs.remove_component(entity, "InWater")
|
||||
if not ecs.has_component(entity, "InWater") then
|
||||
print("Entity is no longer in water")
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Setting and Getting Components with Data
|
||||
-- =============================================================================
|
||||
|
||||
-- Set a component with data (creates or replaces it):
|
||||
ecs.set_component(entity, "Transform", {
|
||||
position = { 10, 20, 30 },
|
||||
rotation = { 1, 0, 0, 0 }, -- quaternion w, x, y, z
|
||||
scale = { 2, 2, 2 }
|
||||
})
|
||||
|
||||
-- Get the component back:
|
||||
local transform = ecs.get_component(entity, "Transform")
|
||||
if transform then
|
||||
print("Position: " .. transform.position[1] .. ", "
|
||||
.. transform.position[2] .. ", "
|
||||
.. transform.position[3])
|
||||
print("Scale: " .. transform.scale[1] .. ", "
|
||||
.. transform.scale[2] .. ", "
|
||||
.. transform.scale[3])
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Individual Field Access
|
||||
-- =============================================================================
|
||||
|
||||
-- Get a single field from a component:
|
||||
local pos_x = ecs.get_field(entity, "Transform", "position")
|
||||
print("Position X from get_field: " .. pos_x[1])
|
||||
|
||||
-- Set a single field:
|
||||
ecs.set_field(entity, "Transform", "position", { 0, 0, 0 })
|
||||
local new_pos = ecs.get_field(entity, "Transform", "position")
|
||||
print("New position: " .. new_pos[1] .. ", " .. new_pos[2] .. ", " .. new_pos[3])
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical Examples: Common Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a player character:
|
||||
function create_player(name, x, y, z)
|
||||
local player = ecs.create_entity()
|
||||
ecs.set_entity_name(player, name)
|
||||
|
||||
-- Transform (position in the world):
|
||||
ecs.set_component(player, "Transform", {
|
||||
position = { x, y, z },
|
||||
rotation = { 1, 0, 0, 0 },
|
||||
scale = { 1, 1, 1 }
|
||||
})
|
||||
|
||||
-- Renderable (visible mesh):
|
||||
ecs.set_component(player, "Renderable", {
|
||||
meshName = "character.mesh",
|
||||
visible = true
|
||||
})
|
||||
|
||||
-- Character (physics capsule for movement):
|
||||
ecs.set_component(player, "Character", {
|
||||
radius = 0.4,
|
||||
height = 1.8,
|
||||
offset = { 0, 0.9, 0 },
|
||||
enabled = true,
|
||||
useGravity = true
|
||||
})
|
||||
|
||||
-- PlayerController (camera and input):
|
||||
ecs.set_component(player, "PlayerController", {
|
||||
cameraMode = 1,
|
||||
tpsDistance = 5.0,
|
||||
tpsHeight = 2.0,
|
||||
mouseSensitivity = 0.5
|
||||
})
|
||||
|
||||
-- Inventory:
|
||||
ecs.set_component(player, "Inventory", {
|
||||
maxSlots = 20,
|
||||
maxWeight = 50.0,
|
||||
isContainer = true
|
||||
})
|
||||
|
||||
print("Created player: " .. name)
|
||||
return player
|
||||
end
|
||||
|
||||
local hero = create_player("Hero", 0, 0, 0)
|
||||
|
||||
-- Create a light source:
|
||||
function create_light(name, x, y, z, light_type)
|
||||
local light = ecs.create_entity()
|
||||
ecs.set_entity_name(light, name)
|
||||
|
||||
ecs.set_component(light, "Transform", {
|
||||
position = { x, y, z },
|
||||
rotation = { 1, 0, 0, 0 },
|
||||
scale = { 1, 1, 1 }
|
||||
})
|
||||
|
||||
ecs.set_component(light, "Light", {
|
||||
lightType = light_type or 0, -- 0=point, 1=directional, 2=spot
|
||||
diffuseColor = { 1, 1, 1, 1 },
|
||||
intensity = 1.5,
|
||||
range = 100,
|
||||
castShadows = true
|
||||
})
|
||||
|
||||
print("Created light: " .. name)
|
||||
return light
|
||||
end
|
||||
|
||||
local sun = create_light("Sun", 0, 100, 0, 1) -- directional light
|
||||
|
||||
-- Create a building with smart object interaction:
|
||||
function create_building(name, x, z)
|
||||
local building = ecs.create_entity()
|
||||
ecs.set_entity_name(building, name)
|
||||
|
||||
ecs.set_component(building, "Transform", {
|
||||
position = { x, 0, z },
|
||||
rotation = { 1, 0, 0, 0 },
|
||||
scale = { 1, 1, 1 }
|
||||
})
|
||||
|
||||
ecs.set_component(building, "Renderable", {
|
||||
meshName = "building.mesh",
|
||||
visible = true
|
||||
})
|
||||
|
||||
ecs.set_component(building, "SmartObject", {
|
||||
radius = 2.0,
|
||||
height = 3.0,
|
||||
actionNames = { "enter", "exit" }
|
||||
})
|
||||
|
||||
-- RigidBody for physics:
|
||||
ecs.set_component(building, "RigidBody", {
|
||||
bodyType = 0, -- 0=static
|
||||
mass = 0,
|
||||
friction = 0.5,
|
||||
restitution = 0.1,
|
||||
enabled = true
|
||||
})
|
||||
|
||||
print("Created building: " .. name)
|
||||
return building
|
||||
end
|
||||
|
||||
local house = create_building("House", 10, 10)
|
||||
|
||||
-- Create an item:
|
||||
function create_item(name, item_id, x, y, z)
|
||||
local item = ecs.create_entity()
|
||||
ecs.set_entity_name(item, name)
|
||||
|
||||
ecs.set_component(item, "Transform", {
|
||||
position = { x, y, z },
|
||||
rotation = { 1, 0, 0, 0 },
|
||||
scale = { 0.5, 0.5, 0.5 }
|
||||
})
|
||||
|
||||
ecs.set_component(item, "Renderable", {
|
||||
meshName = "potion.mesh",
|
||||
visible = true
|
||||
})
|
||||
|
||||
ecs.set_component(item, "Item", {
|
||||
itemName = name,
|
||||
itemType = "consumable",
|
||||
itemId = item_id,
|
||||
stackSize = 1,
|
||||
maxStackSize = 10,
|
||||
weight = 0.5,
|
||||
value = 50,
|
||||
useActionName = "drink_potion"
|
||||
})
|
||||
|
||||
print("Created item: " .. name)
|
||||
return item
|
||||
end
|
||||
|
||||
local potion = create_item("Health Potion", "potion_health", 5, 0.5, 5)
|
||||
|
||||
-- Create a NavMesh area:
|
||||
function create_navmesh_area(name)
|
||||
local nav = ecs.create_entity()
|
||||
ecs.set_entity_name(nav, name)
|
||||
|
||||
ecs.set_component(nav, "NavMesh", {
|
||||
cellSize = 0.3,
|
||||
cellHeight = 0.2,
|
||||
agentHeight = 2.0,
|
||||
agentRadius = 0.5,
|
||||
agentMaxClimb = 0.5,
|
||||
agentMaxSlope = 45.0,
|
||||
enabled = true
|
||||
})
|
||||
|
||||
print("Created NavMesh: " .. name)
|
||||
return nav
|
||||
end
|
||||
|
||||
local navmesh = create_navmesh_area("MainNavMesh")
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Tag Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Tag components are boolean markers with no data fields.
|
||||
-- Common tags include:
|
||||
-- EditorMarker - marks entities visible in the editor
|
||||
-- InWater - entity is currently in water
|
||||
-- GeneratedPhysicsTag - physics was auto-generated
|
||||
-- ParentComponent - entity is a parent in a hierarchy
|
||||
-- NavMeshGeometrySource - entity contributes to navmesh generation
|
||||
|
||||
-- Example: Mark entities for different purposes:
|
||||
local marker1 = ecs.create_entity()
|
||||
ecs.add_component(marker1, "GeneratedPhysicsTag")
|
||||
|
||||
local marker2 = ecs.create_entity()
|
||||
ecs.add_component(marker2, "NavMeshGeometrySource")
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with GOAP Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create an NPC with GOAP AI:
|
||||
function create_npc(name, x, z)
|
||||
local npc = ecs.create_entity()
|
||||
ecs.set_entity_name(npc, name)
|
||||
|
||||
ecs.set_component(npc, "Transform", {
|
||||
position = { x, 0, z },
|
||||
rotation = { 1, 0, 0, 0 },
|
||||
scale = { 1, 1, 1 }
|
||||
})
|
||||
|
||||
ecs.set_component(npc, "Character", {
|
||||
radius = 0.4,
|
||||
height = 1.8,
|
||||
offset = { 0, 0.9, 0 },
|
||||
enabled = true,
|
||||
useGravity = true
|
||||
})
|
||||
|
||||
-- GOAP blackboard (character's knowledge about the world):
|
||||
ecs.set_component(npc, "GoapBlackboard", {
|
||||
values = {
|
||||
health = 100,
|
||||
stamina = 100,
|
||||
hunger = 0,
|
||||
wood_count = 0
|
||||
},
|
||||
floatValues = {
|
||||
hunger = 0.0
|
||||
},
|
||||
stringValues = {
|
||||
state = "idle"
|
||||
}
|
||||
})
|
||||
|
||||
-- GOAP planner (plans actions to achieve goals):
|
||||
ecs.set_component(npc, "GoapPlanner", {
|
||||
enabled = true,
|
||||
maxIterations = 100
|
||||
})
|
||||
|
||||
-- GOAP runner (executes the plan):
|
||||
ecs.set_component(npc, "GoapRunner", {
|
||||
enabled = true,
|
||||
currentAction = "idle"
|
||||
})
|
||||
|
||||
-- Behavior tree for idle behavior:
|
||||
ecs.set_component(npc, "BehaviorTree", {
|
||||
treeName = "idle_behavior",
|
||||
enabled = true
|
||||
})
|
||||
|
||||
-- Path following for movement:
|
||||
ecs.set_component(npc, "PathFollowing", {
|
||||
enabled = true,
|
||||
speed = 1.5
|
||||
})
|
||||
|
||||
-- NavMesh agent for navigation:
|
||||
ecs.set_component(npc, "NavMeshAgent", {
|
||||
enabled = true,
|
||||
radius = 0.5,
|
||||
height = 2.0
|
||||
})
|
||||
|
||||
print("Created NPC: " .. name)
|
||||
return npc
|
||||
end
|
||||
|
||||
local npc = create_npc("Villager_01", -10, -10)
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Event Handler Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Add event handlers to entities:
|
||||
ecs.set_component(npc, "EventHandler", {
|
||||
eventName = "collision",
|
||||
actionName = "handle_collision",
|
||||
enabled = true
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Dialogue Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Add dialogue to an NPC:
|
||||
ecs.set_component(npc, "Dialogue", {
|
||||
text = "Hello there, traveler!",
|
||||
speaker = "Villager_01",
|
||||
enabled = true
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Water Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a water plane:
|
||||
function create_water(name, y)
|
||||
local water = ecs.create_entity()
|
||||
ecs.set_entity_name(water, name)
|
||||
|
||||
ecs.set_component(water, "WaterPlane", {
|
||||
enabled = true,
|
||||
waterSurfaceY = y or 0.0,
|
||||
planeSize = 1000,
|
||||
reflectivity = 0.5,
|
||||
waveSpeed = 1.0
|
||||
})
|
||||
|
||||
ecs.set_component(water, "WaterPhysics", {
|
||||
enabled = true,
|
||||
waveHeight = 0.5
|
||||
})
|
||||
|
||||
print("Created water: " .. name)
|
||||
return water
|
||||
end
|
||||
|
||||
local ocean = create_water("Ocean", 0.0)
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Sky and Sun Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a skybox:
|
||||
function create_sky(name)
|
||||
local sky = ecs.create_entity()
|
||||
ecs.set_entity_name(sky, name)
|
||||
|
||||
ecs.set_component(sky, "Skybox", {
|
||||
enabled = true,
|
||||
size = 500,
|
||||
starsEnabled = true
|
||||
})
|
||||
|
||||
print("Created sky: " .. name)
|
||||
return sky
|
||||
end
|
||||
|
||||
local skybox = create_sky("Skybox")
|
||||
|
||||
-- Update sun properties:
|
||||
ecs.set_component(sun, "Sun", {
|
||||
enabled = true,
|
||||
timeOfDay = 12.0,
|
||||
timeSpeed = 1.0,
|
||||
intensity = 1.0,
|
||||
castShadows = true
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Camera Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a camera:
|
||||
function create_camera(name)
|
||||
local cam = ecs.create_entity()
|
||||
ecs.set_entity_name(cam, name)
|
||||
|
||||
ecs.set_component(cam, "Camera", {
|
||||
fovY = 60,
|
||||
nearClip = 0.1,
|
||||
farClip = 1000,
|
||||
orthographic = false
|
||||
})
|
||||
|
||||
print("Created camera: " .. name)
|
||||
return cam
|
||||
end
|
||||
|
||||
local camera = create_camera("MainCamera")
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Prefab Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Mark an entity as a prefab instance:
|
||||
ecs.set_component(house, "PrefabInstance", {
|
||||
prefabPath = "prefabs/house.json",
|
||||
instantiated = true
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with CellGrid Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a cell grid for spatial partitioning:
|
||||
function create_cell_grid(name, width, height, depth)
|
||||
local grid = ecs.create_entity()
|
||||
ecs.set_entity_name(grid, name)
|
||||
|
||||
ecs.set_component(grid, "CellGrid", {
|
||||
width = width or 10,
|
||||
height = height or 5,
|
||||
depth = depth or 10,
|
||||
cellSize = 1.0,
|
||||
cellHeight = 0.5
|
||||
})
|
||||
|
||||
print("Created cell grid: " .. name)
|
||||
return grid
|
||||
end
|
||||
|
||||
local grid = create_cell_grid("WorldGrid", 20, 10, 20)
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Town/District/Lot/Room Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a town with districts, lots, and rooms:
|
||||
function create_town(name)
|
||||
local town = ecs.create_entity()
|
||||
ecs.set_entity_name(town, name)
|
||||
|
||||
ecs.set_component(town, "Town", {
|
||||
townName = name,
|
||||
population = 500
|
||||
})
|
||||
|
||||
-- Create a district:
|
||||
local district = ecs.create_entity()
|
||||
ecs.set_entity_name(district, "Market District")
|
||||
ecs.set_component(district, "District", {
|
||||
districtName = "Market District",
|
||||
districtType = "commercial"
|
||||
})
|
||||
|
||||
-- Create a lot:
|
||||
local lot = ecs.create_entity()
|
||||
ecs.set_entity_name(lot, "Residential Lot 1")
|
||||
ecs.set_component(lot, "Lot", {
|
||||
lotName = "Residential Lot 1",
|
||||
lotType = "residential",
|
||||
width = 20,
|
||||
depth = 30
|
||||
})
|
||||
|
||||
-- Create a room:
|
||||
local room = ecs.create_entity()
|
||||
ecs.set_entity_name(room, "Kitchen")
|
||||
ecs.set_component(room, "Room", {
|
||||
roomName = "Kitchen",
|
||||
roomType = "kitchen",
|
||||
floor = 0
|
||||
})
|
||||
|
||||
print("Created town: " .. name)
|
||||
return town
|
||||
end
|
||||
|
||||
local rivendell = create_town("Rivendell")
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Furniture Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a furniture template:
|
||||
function create_furniture(name, mesh, category)
|
||||
local furniture = ecs.create_entity()
|
||||
ecs.set_entity_name(furniture, name)
|
||||
|
||||
ecs.set_component(furniture, "FurnitureTemplate", {
|
||||
templateName = name,
|
||||
meshName = mesh or "chair.mesh",
|
||||
category = category or "seating"
|
||||
})
|
||||
|
||||
print("Created furniture: " .. name)
|
||||
return furniture
|
||||
end
|
||||
|
||||
local chair = create_furniture("Wooden Chair", "chair.mesh", "seating")
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Animation Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Add animation tree to a character:
|
||||
ecs.set_component(npc, "AnimationTree", {
|
||||
treeName = "humanoid",
|
||||
enabled = true
|
||||
})
|
||||
|
||||
ecs.set_component(npc, "AnimationTreeTemplate", {
|
||||
templateName = "humanoid_base",
|
||||
blendTime = 0.2
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Physics Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Add a physics collider to an entity:
|
||||
ecs.set_component(house, "PhysicsCollider", {
|
||||
shapeType = "box",
|
||||
size = { 5, 3, 5 },
|
||||
enabled = true
|
||||
})
|
||||
|
||||
-- Add buoyancy info for water physics:
|
||||
ecs.set_component(house, "BuoyancyInfo", {
|
||||
enabled = true,
|
||||
buoyancy = 1.0,
|
||||
linearDrag = 0.5,
|
||||
angularDrag = 0.3
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with LOD Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Add LOD settings:
|
||||
ecs.set_component(entity, "Lod", {
|
||||
lodLevel = 0,
|
||||
distance = 100.0
|
||||
})
|
||||
|
||||
ecs.set_component(entity, "LodSettings", {
|
||||
enabled = true,
|
||||
lodBias = 1.0
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Static Geometry Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Batch entities into static geometry:
|
||||
ecs.set_component(entity, "StaticGeometry", {
|
||||
batchName = "forest_batch",
|
||||
enabled = true
|
||||
})
|
||||
|
||||
ecs.set_component(entity, "StaticGeometryMember", {
|
||||
parentBatch = "forest_batch",
|
||||
enabled = true
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Procedural Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create procedural textures and materials:
|
||||
ecs.set_component(entity, "ProceduralTexture", {
|
||||
textureName = "grass",
|
||||
width = 512,
|
||||
height = 512
|
||||
})
|
||||
|
||||
ecs.set_component(entity, "ProceduralMaterial", {
|
||||
materialName = "ground_mat",
|
||||
baseColor = { 0.5, 0.5, 0.5, 1.0 }
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Primitive Components
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a primitive shape:
|
||||
ecs.set_component(entity, "Primitive", {
|
||||
primitiveType = "box",
|
||||
size = { 1, 2, 1 }
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Triangle Buffer Components
|
||||
-- =============================================================================
|
||||
|
||||
ecs.set_component(entity, "TriangleBuffer", {
|
||||
enabled = true,
|
||||
vertexCount = 100
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Character Slots
|
||||
-- =============================================================================
|
||||
|
||||
ecs.set_component(entity, "CharacterSlots", {
|
||||
slotCount = 8
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Roof Components
|
||||
-- =============================================================================
|
||||
|
||||
ecs.set_component(entity, "Roof", {
|
||||
roofType = "gable",
|
||||
height = 2.5,
|
||||
enabled = true
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with ClearArea Components
|
||||
-- =============================================================================
|
||||
|
||||
ecs.set_component(entity, "ClearArea", {
|
||||
radius = 5.0,
|
||||
enabled = true
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Action Database Components
|
||||
-- =============================================================================
|
||||
|
||||
ecs.set_component(entity, "ActionDatabase", {
|
||||
enabled = true
|
||||
})
|
||||
|
||||
ecs.set_component(entity, "ActionDebug", {
|
||||
enabled = true,
|
||||
showDebugInfo = true
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Working with Startup Menu Components
|
||||
-- =============================================================================
|
||||
|
||||
ecs.set_component(entity, "StartupMenu", {
|
||||
enabled = true,
|
||||
showOnStart = true
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Component Lifecycle Summary
|
||||
-- =============================================================================
|
||||
|
||||
-- Components can be:
|
||||
-- 1. Added: ecs.add_component(entity, "ComponentName")
|
||||
-- 2. Removed: ecs.remove_component(entity, "ComponentName")
|
||||
-- 3. Checked: ecs.has_component(entity, "ComponentName")
|
||||
-- 4. Get: ecs.get_component(entity, "ComponentName")
|
||||
-- 5. Set: ecs.set_component(entity, "ComponentName", { fields... })
|
||||
-- 6. Field: ecs.get_field(entity, "ComponentName", "fieldName")
|
||||
-- 7. Field: ecs.set_field(entity, "ComponentName", "fieldName", value)
|
||||
|
||||
print("Component API examples completed successfully!")
|
||||
177
src/features/editScene/lua-examples/entity_example.lua
Normal file
177
src/features/editScene/lua-examples/entity_example.lua
Normal file
@@ -0,0 +1,177 @@
|
||||
-- =============================================================================
|
||||
-- Entity Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to create, manage, and query entities from Lua
|
||||
-- using the ecs.* Lua API.
|
||||
--
|
||||
-- Entities are the fundamental building blocks of the ECS world. Every object
|
||||
-- in the game world is an entity with a unique numeric ID.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Creating Entities
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a new entity. Returns a numeric ID.
|
||||
local player = ecs.create_entity()
|
||||
print("Created entity with ID: " .. player)
|
||||
|
||||
-- Create multiple entities:
|
||||
local npc1 = ecs.create_entity()
|
||||
local npc2 = ecs.create_entity()
|
||||
local item = ecs.create_entity()
|
||||
|
||||
-- =============================================================================
|
||||
-- Entity Existence and Lifecycle
|
||||
-- =============================================================================
|
||||
|
||||
-- Check if an entity exists:
|
||||
if ecs.entity_exists(player) then
|
||||
print("Player entity exists")
|
||||
end
|
||||
|
||||
-- Check a non-existent entity:
|
||||
if not ecs.entity_exists(999999) then
|
||||
print("Entity 999999 does not exist")
|
||||
end
|
||||
|
||||
-- Destroy an entity:
|
||||
ecs.destroy_entity(npc2)
|
||||
if not ecs.entity_exists(npc2) then
|
||||
print("npc2 was destroyed")
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Entity Names
|
||||
-- =============================================================================
|
||||
|
||||
-- Set an entity's name:
|
||||
ecs.set_entity_name(player, "Hero")
|
||||
ecs.set_entity_name(npc1, "Villager_01")
|
||||
ecs.set_entity_name(item, "Health_Potion")
|
||||
|
||||
-- Get an entity's name:
|
||||
local name = ecs.get_entity_name(player)
|
||||
print("Player name: " .. name)
|
||||
|
||||
-- Look up an entity by name:
|
||||
local found = ecs.get_entity_by_name("Villager_01")
|
||||
if found then
|
||||
print("Found entity " .. found .. " with name 'Villager_01'")
|
||||
end
|
||||
|
||||
-- Look up a non-existent name:
|
||||
local not_found = ecs.get_entity_by_name("Nonexistent")
|
||||
if not_found == nil then
|
||||
print("'Nonexistent' not found (returns nil)")
|
||||
end
|
||||
|
||||
-- =============================================================================
|
||||
-- Entity Hierarchy (Parent/Children)
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a parent-child hierarchy:
|
||||
local house = ecs.create_entity()
|
||||
ecs.set_entity_name(house, "House")
|
||||
|
||||
local door = ecs.create_entity()
|
||||
ecs.set_entity_name(door, "Door")
|
||||
|
||||
local window = ecs.create_entity()
|
||||
ecs.set_entity_name(window, "Window")
|
||||
|
||||
-- Check parent of a child (initially none):
|
||||
local p = ecs.parent(door)
|
||||
if p == nil then
|
||||
print("Door has no parent initially")
|
||||
end
|
||||
|
||||
-- Check children of a parent (initially none):
|
||||
local kids = ecs.children(house)
|
||||
print("House has " .. #kids .. " children initially")
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical Example: Creating a Scene
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a complete scene with entities:
|
||||
function create_scene()
|
||||
-- Create terrain
|
||||
local terrain = ecs.create_entity()
|
||||
ecs.set_entity_name(terrain, "Terrain")
|
||||
|
||||
-- Create lighting
|
||||
local sun = ecs.create_entity()
|
||||
ecs.set_entity_name(sun, "Sun")
|
||||
|
||||
-- Create buildings
|
||||
local buildings = {}
|
||||
for i = 1, 3 do
|
||||
local bldg = ecs.create_entity()
|
||||
ecs.set_entity_name(bldg, "Building_" .. i)
|
||||
table.insert(buildings, bldg)
|
||||
end
|
||||
|
||||
-- Create characters
|
||||
local characters = {}
|
||||
for i = 1, 5 do
|
||||
local char = ecs.create_entity()
|
||||
ecs.set_entity_name(char, "Character_" .. i)
|
||||
table.insert(characters, char)
|
||||
end
|
||||
|
||||
print("Scene created with:")
|
||||
print(" 1 terrain entity")
|
||||
print(" 1 sun entity")
|
||||
print(" " .. #buildings .. " buildings")
|
||||
print(" " .. #characters .. " characters")
|
||||
|
||||
return {
|
||||
terrain = terrain,
|
||||
sun = sun,
|
||||
buildings = buildings,
|
||||
characters = characters
|
||||
}
|
||||
end
|
||||
|
||||
local scene = create_scene()
|
||||
|
||||
-- =============================================================================
|
||||
-- Entity ID Properties
|
||||
-- =============================================================================
|
||||
|
||||
-- Entity IDs are positive integers:
|
||||
local e = ecs.create_entity()
|
||||
assert(type(e) == "number", "Entity ID should be a number")
|
||||
assert(e > 0, "Entity ID should be positive")
|
||||
|
||||
-- Each entity gets a unique ID:
|
||||
local ids = {}
|
||||
for i = 1, 10 do
|
||||
ids[i] = ecs.create_entity()
|
||||
end
|
||||
for i = 1, 10 do
|
||||
for j = i + 1, 10 do
|
||||
assert(ids[i] ~= ids[j], "IDs should be unique")
|
||||
end
|
||||
end
|
||||
print("All 10 entities have unique IDs")
|
||||
|
||||
-- Destroyed entity IDs are not reused immediately:
|
||||
local old_id = ecs.create_entity()
|
||||
ecs.destroy_entity(old_id)
|
||||
local new_id = ecs.create_entity()
|
||||
assert(new_id ~= old_id, "New entity should have different ID")
|
||||
print("Destroyed entity ID " .. old_id .. " is not reused")
|
||||
|
||||
-- =============================================================================
|
||||
-- Error Handling
|
||||
-- =============================================================================
|
||||
|
||||
-- These operations should not crash:
|
||||
ecs.destroy_entity(999999) -- destroying non-existent entity is safe
|
||||
ecs.set_entity_name(999999, "ghost") -- setting name on non-existent entity
|
||||
local n = ecs.get_entity_name(999999) -- returns empty string
|
||||
print("Name of non-existent entity: '" .. n .. "'")
|
||||
|
||||
print("Entity API examples completed successfully!")
|
||||
285
src/features/editScene/lua-examples/event_example.lua
Normal file
285
src/features/editScene/lua-examples/event_example.lua
Normal file
@@ -0,0 +1,285 @@
|
||||
-- =============================================================================
|
||||
-- Event Lua API Examples
|
||||
-- =============================================================================
|
||||
-- This file demonstrates how to subscribe to, send, and manage events from
|
||||
-- Lua using the ecs.* Lua API.
|
||||
--
|
||||
-- Events are a publish/subscribe mechanism. Any part of the game can send
|
||||
-- an event, and any subscriber can react to it. This decouples systems
|
||||
-- from each other.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- Subscribing to Events
|
||||
-- =============================================================================
|
||||
|
||||
-- Subscribe to an event. Returns a subscription ID (number) that can be
|
||||
-- used to unsubscribe later.
|
||||
local sub_id = ecs.subscribe_event("hello", function(event, params)
|
||||
print("Received event: " .. event)
|
||||
end)
|
||||
|
||||
print("Subscribed to 'hello' with ID: " .. sub_id)
|
||||
|
||||
-- =============================================================================
|
||||
-- Sending Events
|
||||
-- =============================================================================
|
||||
|
||||
-- Send a simple event with no parameters:
|
||||
ecs.send_event("hello")
|
||||
|
||||
-- Send an event with parameters:
|
||||
ecs.send_event("hello", {
|
||||
values = { count = 42 },
|
||||
stringValues = { message = "Hello World!" }
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Event Callback Parameters
|
||||
-- =============================================================================
|
||||
|
||||
-- The callback receives two arguments:
|
||||
-- 1. event - the event name (string)
|
||||
-- 2. params - a table with the event data (or nil if no data was sent)
|
||||
|
||||
ecs.subscribe_event("player_damaged", function(event, params)
|
||||
print("Event: " .. event)
|
||||
if params then
|
||||
if params.values then
|
||||
print(" Damage: " .. (params.values.damage or 0))
|
||||
print(" Health remaining: " .. (params.values.health or 0))
|
||||
end
|
||||
if params.stringValues then
|
||||
print(" Source: " .. (params.stringValues.source or "unknown"))
|
||||
end
|
||||
if params.vec3Values then
|
||||
local pos = params.vec3Values.position
|
||||
if pos then
|
||||
print(" Position: " .. pos[1] .. ", " .. pos[2] .. ", " .. pos[3])
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Send a damage event:
|
||||
ecs.send_event("player_damaged", {
|
||||
values = { damage = 25, health = 75 },
|
||||
stringValues = { source = "goblin_archer" },
|
||||
vec3Values = { position = { 10, 0, 20 } }
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Unsubscribing from Events
|
||||
-- =============================================================================
|
||||
|
||||
-- When you no longer need to listen to an event, unsubscribe:
|
||||
local temp_sub = ecs.subscribe_event("temporary", function()
|
||||
print("This should not be printed after unsubscribe")
|
||||
end)
|
||||
|
||||
ecs.send_event("temporary") -- callback fires
|
||||
|
||||
ecs.unsubscribe_event(temp_sub)
|
||||
|
||||
ecs.send_event("temporary") -- callback does NOT fire (unsubscribed)
|
||||
|
||||
-- =============================================================================
|
||||
-- Multiple Subscribers
|
||||
-- =============================================================================
|
||||
|
||||
-- Multiple subscribers can listen to the same event. All of them will be
|
||||
-- called when the event is sent.
|
||||
|
||||
local count_a = 0
|
||||
local count_b = 0
|
||||
|
||||
local sub_a = ecs.subscribe_event("multi", function()
|
||||
count_a = count_a + 1
|
||||
print("Subscriber A called (total: " .. count_a .. ")")
|
||||
end)
|
||||
|
||||
local sub_b = ecs.subscribe_event("multi", function()
|
||||
count_b = count_b + 1
|
||||
print("Subscriber B called (total: " .. count_b .. ")")
|
||||
end)
|
||||
|
||||
ecs.send_event("multi") -- both A and B fire
|
||||
ecs.send_event("multi") -- both fire again
|
||||
|
||||
-- =============================================================================
|
||||
-- Event Parameter Types
|
||||
-- =============================================================================
|
||||
|
||||
-- Events can carry various types of data in their params table:
|
||||
|
||||
ecs.subscribe_event("data_event", function(event, params)
|
||||
print("Received data_event with:")
|
||||
if params then
|
||||
-- Integer values:
|
||||
if params.values then
|
||||
for k, v in pairs(params.values) do
|
||||
print(" int " .. k .. " = " .. v)
|
||||
end
|
||||
end
|
||||
-- Float values:
|
||||
if params.floatValues then
|
||||
for k, v in pairs(params.floatValues) do
|
||||
print(" float " .. k .. " = " .. v)
|
||||
end
|
||||
end
|
||||
-- String values:
|
||||
if params.stringValues then
|
||||
for k, v in pairs(params.stringValues) do
|
||||
print(" string " .. k .. " = '" .. v .. "'")
|
||||
end
|
||||
end
|
||||
-- Vec3 values:
|
||||
if params.vec3Values then
|
||||
for k, v in pairs(params.vec3Values) do
|
||||
print(" vec3 " .. k .. " = (" .. v[1] .. ", " .. v[2] .. ", " .. v[3] .. ")")
|
||||
end
|
||||
end
|
||||
-- Bit flags:
|
||||
if params.bits ~= nil then
|
||||
print(" bits = " .. params.bits)
|
||||
end
|
||||
if params.mask ~= nil then
|
||||
print(" mask = " .. params.mask)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Send an event with all parameter types:
|
||||
ecs.send_event("data_event", {
|
||||
values = { score = 100, level = 5, kills = 42 },
|
||||
floatValues = { speed = 1.5, health = 75.5 },
|
||||
stringValues = { name = "Hero", state = "exploring" },
|
||||
vec3Values = { position = { 10, 20, 30 }, velocity = { 1, 0, 0 } },
|
||||
bits = 5,
|
||||
mask = 7
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Practical Example: Game Event System
|
||||
-- =============================================================================
|
||||
|
||||
-- Create a simple game event system:
|
||||
|
||||
local event_handlers = {}
|
||||
|
||||
function register_game_handlers()
|
||||
-- Quest events:
|
||||
event_handlers.quest_complete = ecs.subscribe_event("quest_complete", function(event, params)
|
||||
local quest_name = params and params.stringValues and params.stringValues.quest_name or "unknown"
|
||||
local reward_xp = params and params.values and params.values.reward_xp or 0
|
||||
print("Quest completed: " .. quest_name .. " (+" .. reward_xp .. " XP)")
|
||||
end)
|
||||
|
||||
-- Combat events:
|
||||
event_handlers.enemy_killed = ecs.subscribe_event("enemy_killed", function(event, params)
|
||||
local enemy = params and params.stringValues and params.stringValues.enemy_type or "unknown"
|
||||
local xp = params and params.values and params.values.xp_reward or 0
|
||||
print("Killed " .. enemy .. " (+" .. xp .. " XP)")
|
||||
end)
|
||||
|
||||
-- Item events:
|
||||
event_handlers.item_picked_up = ecs.subscribe_event("item_picked_up", function(event, params)
|
||||
local item = params and params.stringValues and params.stringValues.item_name or "unknown"
|
||||
local count = params and params.values and params.values.count or 1
|
||||
print("Picked up " .. count .. "x " .. item)
|
||||
end)
|
||||
|
||||
-- Dialogue events:
|
||||
event_handlers.dialogue_started = ecs.subscribe_event("dialogue_started", function(event, params)
|
||||
local npc = params and params.stringValues and params.stringValues.npc_name or "unknown"
|
||||
print("Started dialogue with " .. npc)
|
||||
end)
|
||||
|
||||
-- Environment events:
|
||||
event_handlers.time_changed = ecs.subscribe_event("time_changed", function(event, params)
|
||||
local hour = params and params.values and params.values.hour or 0
|
||||
local minute = params and params.values and params.values.minute or 0
|
||||
print("Time changed to " .. hour .. ":" .. string.format("%02d", minute))
|
||||
end)
|
||||
|
||||
-- Player events:
|
||||
event_handlers.player_died = ecs.subscribe_event("player_died", function(event, params)
|
||||
local killer = params and params.stringValues and params.stringValues.killed_by or "unknown"
|
||||
print("Player was killed by " .. killer .. "!")
|
||||
end)
|
||||
|
||||
print("All game event handlers registered")
|
||||
end
|
||||
|
||||
register_game_handlers()
|
||||
|
||||
-- Simulate some game events:
|
||||
ecs.send_event("quest_complete", {
|
||||
stringValues = { quest_name = "The Lost Artifact" },
|
||||
values = { reward_xp = 500 }
|
||||
})
|
||||
|
||||
ecs.send_event("enemy_killed", {
|
||||
stringValues = { enemy_type = "Goblin Warrior" },
|
||||
values = { xp_reward = 50 }
|
||||
})
|
||||
|
||||
ecs.send_event("item_picked_up", {
|
||||
stringValues = { item_name = "Health Potion" },
|
||||
values = { count = 2 }
|
||||
})
|
||||
|
||||
ecs.send_event("dialogue_started", {
|
||||
stringValues = { npc_name = "Elder Marcus" }
|
||||
})
|
||||
|
||||
ecs.send_event("time_changed", {
|
||||
values = { hour = 18, minute = 30 }
|
||||
})
|
||||
|
||||
-- =============================================================================
|
||||
-- Cleanup: Unsubscribe All
|
||||
-- =============================================================================
|
||||
|
||||
-- When cleaning up, unsubscribe all registered handlers:
|
||||
function cleanup_event_handlers()
|
||||
for name, id in pairs(event_handlers) do
|
||||
ecs.unsubscribe_event(id)
|
||||
print("Unsubscribed from: " .. name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Uncomment to clean up:
|
||||
-- cleanup_event_handlers()
|
||||
|
||||
-- =============================================================================
|
||||
-- Event API Reference
|
||||
-- =============================================================================
|
||||
--
|
||||
-- ecs.subscribe_event(event_name, callback)
|
||||
-- Subscribe to an event. Returns a subscription ID (number).
|
||||
-- Parameters:
|
||||
-- event_name - string, the name of the event to listen for
|
||||
-- callback - function(event, params), called when the event fires
|
||||
-- Returns: subscription ID (number)
|
||||
--
|
||||
-- ecs.unsubscribe_event(subscription_id)
|
||||
-- Unsubscribe from an event.
|
||||
-- Parameters:
|
||||
-- subscription_id - number, the ID returned by subscribe_event
|
||||
-- Safe to call with an invalid ID (no crash).
|
||||
--
|
||||
-- ecs.send_event(event_name, params)
|
||||
-- Send an event to all subscribers.
|
||||
-- Parameters:
|
||||
-- event_name - string, the name of the event to send
|
||||
-- params - optional table with event data:
|
||||
-- .values - table of integer key-value pairs
|
||||
-- .floatValues - table of float key-value pairs
|
||||
-- .stringValues - table of string key-value pairs
|
||||
-- .vec3Values - table of vec3 key-value pairs (each vec3 is {x, y, z})
|
||||
-- .bits - integer bit flags
|
||||
-- .mask - integer bit mask
|
||||
-- =============================================================================
|
||||
|
||||
print("Event API examples completed successfully!")
|
||||
@@ -54,6 +54,8 @@
|
||||
#include "components/EventHandler.hpp"
|
||||
#include "components/Item.hpp"
|
||||
#include "components/Inventory.hpp"
|
||||
#include "components/GeneratedPhysicsTag.hpp"
|
||||
#include "components/Relationship.hpp"
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
@@ -1683,6 +1685,67 @@ static void registerAllComponents()
|
||||
if (lua_getfield(L, idx, "lastResult"), lua_isstring(L, -1))
|
||||
c.lastResult = lua_tostring(L, -1);
|
||||
lua_pop(L, 1););
|
||||
|
||||
// --- GeneratedPhysicsTag (tag, no fields) ---
|
||||
s_components["GeneratedPhysicsTag"] = {
|
||||
"GeneratedPhysicsTag",
|
||||
[](lua_State *L, flecs::entity e) {
|
||||
lua_pushboolean(L,
|
||||
e.has<GeneratedPhysicsTag>() ? 1 : 0);
|
||||
},
|
||||
[](lua_State *L, flecs::entity e, int idx) {
|
||||
(void)idx;
|
||||
if (lua_toboolean(L, -1))
|
||||
e.add<GeneratedPhysicsTag>();
|
||||
else
|
||||
e.remove<GeneratedPhysicsTag>();
|
||||
}
|
||||
};
|
||||
|
||||
// --- ParentComponent ---
|
||||
// Note: ParentComponent stores a flecs::entity which cannot be
|
||||
// directly serialized to Lua. We expose it as a tag (has/doesn't have)
|
||||
// and provide the parent entity via ecs.parent() in the entity API.
|
||||
s_components["ParentComponent"] = {
|
||||
"ParentComponent",
|
||||
[](lua_State *L, flecs::entity e) {
|
||||
lua_pushboolean(L, e.has<ParentComponent>() ? 1 : 0);
|
||||
},
|
||||
[](lua_State *L, flecs::entity e, int idx) {
|
||||
(void)idx;
|
||||
if (lua_toboolean(L, -1))
|
||||
e.add<ParentComponent>();
|
||||
else
|
||||
e.remove<ParentComponent>();
|
||||
}
|
||||
};
|
||||
|
||||
// --- ModifiedComponent ---
|
||||
s_components["ModifiedComponent"] = {
|
||||
"ModifiedComponent",
|
||||
[](lua_State *L, flecs::entity e) {
|
||||
if (!e.has<ModifiedComponent>()) {
|
||||
lua_pushnil(L);
|
||||
return;
|
||||
}
|
||||
const auto &c = e.get<ModifiedComponent>();
|
||||
lua_newtable(L);
|
||||
lua_pushboolean(L, c.modified ? 1 : 0);
|
||||
lua_setfield(L, -2, "modified");
|
||||
},
|
||||
[](lua_State *L, flecs::entity e, int idx) {
|
||||
ModifiedComponent c;
|
||||
if (e.has<ModifiedComponent>())
|
||||
c = e.get<ModifiedComponent>();
|
||||
if (lua_istable(L, idx)) {
|
||||
if (lua_getfield(L, idx, "modified"),
|
||||
lua_isboolean(L, -1))
|
||||
c.modified = lua_toboolean(L, -1) != 0;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
e.set<ModifiedComponent>(c);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -32,9 +32,11 @@
|
||||
* "Lod", "LodSettings", "StaticGeometry", "StaticGeometryMember",
|
||||
* "ProceduralTexture", "ProceduralMaterial", "Primitive",
|
||||
* "TriangleBuffer", "Sun", "Skybox", "WaterPlane", "WaterPhysics",
|
||||
* "BuoyancyInfo", "StartupMenu", "Dialogue", "PlayerController",
|
||||
* "CellGrid", "Room", "ClearArea", "Roof", "Lot", "District",
|
||||
* "Town", "FurnitureTemplate", "PrefabInstance", "EditorMarker"
|
||||
* "BuoyancyInfo", "InWater", "StartupMenu", "Dialogue",
|
||||
* "PlayerController", "CellGrid", "Room", "ClearArea", "Roof",
|
||||
* "Lot", "District", "Town", "FurnitureTemplate", "PrefabInstance",
|
||||
* "EditorMarker", "GeneratedPhysicsTag", "ParentComponent",
|
||||
* "ModifiedComponent"
|
||||
*/
|
||||
|
||||
namespace editScene
|
||||
|
||||
1909
src/features/editScene/tests/component_lua_test.cpp
Normal file
1909
src/features/editScene/tests/component_lua_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
27
src/features/editScene/tests/components/EditorMarker.hpp
Normal file
27
src/features/editScene/tests/components/EditorMarker.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @file EditorMarker.hpp
|
||||
* @brief Stub for standalone tests.
|
||||
*
|
||||
* Provides the EditorMarkerComponent tag used by LuaEntityApi.cpp.
|
||||
* In the real build, this is defined in the editScene components.
|
||||
*/
|
||||
|
||||
#ifndef EDITSCENE_COMPONENTS_EDITORMARKER_HPP
|
||||
#define EDITSCENE_COMPONENTS_EDITORMARKER_HPP
|
||||
|
||||
#include <flecs.h>
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Tag component marking entities as editor-managed.
|
||||
*
|
||||
* In the real build, this is a Flecs tag. For tests, we define
|
||||
* it as an empty struct so LuaEntityApi.cpp can compile.
|
||||
*/
|
||||
struct EditorMarkerComponent {};
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
#endif // EDITSCENE_COMPONENTS_EDITORMARKER_HPP
|
||||
403
src/features/editScene/tests/entity_lua_test.cpp
Normal file
403
src/features/editScene/tests/entity_lua_test.cpp
Normal file
@@ -0,0 +1,403 @@
|
||||
/**
|
||||
* @file entity_lua_test.cpp
|
||||
* @brief Standalone test for the Lua Entity API.
|
||||
*
|
||||
* Tests entity creation, destruction, naming, hierarchy (parent/children),
|
||||
* and entity lookup functions exposed via the ecs.* Lua API.
|
||||
*
|
||||
* Build with:
|
||||
* g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
|
||||
* entity_lua_test.cpp \
|
||||
* ../lua/LuaEntityApi.cpp \
|
||||
* ../../lua/lua-5.4.8/src/liblua.a \
|
||||
* -o entity_lua_test -lm
|
||||
*
|
||||
* Or via CMake (see CMakeLists.txt in this directory).
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Ogre stub (provides Ogre::String, Ogre::Vector3, Ogre::LogManager)
|
||||
#include "ogre_stub.h"
|
||||
|
||||
// Include Lua
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
}
|
||||
|
||||
// Flecs stub for standalone testing
|
||||
// We provide minimal Flecs types needed by LuaEntityApi
|
||||
namespace flecs
|
||||
{
|
||||
|
||||
// Minimal entity wrapper
|
||||
struct entity {
|
||||
uint64_t m_id = 0;
|
||||
bool m_valid = false;
|
||||
|
||||
entity()
|
||||
: m_id(0)
|
||||
, m_valid(false)
|
||||
{
|
||||
}
|
||||
explicit entity(uint64_t id)
|
||||
: m_id(id)
|
||||
, m_valid(true)
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
bool is_valid() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
bool is_alive() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
const char *name() const
|
||||
{
|
||||
return "";
|
||||
}
|
||||
void set_name(const char *)
|
||||
{
|
||||
}
|
||||
void destruct()
|
||||
{
|
||||
m_valid = false;
|
||||
}
|
||||
|
||||
entity parent() const
|
||||
{
|
||||
return entity();
|
||||
}
|
||||
|
||||
template <typename Func> void children(Func) const
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T> void add()
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T> bool has() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T> const T *get() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T> void set(const T &)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const entity &other) const
|
||||
{
|
||||
return m_id == other.m_id;
|
||||
}
|
||||
};
|
||||
|
||||
using entity_t = uint64_t;
|
||||
|
||||
// Minimal world stub
|
||||
struct world {
|
||||
entity make_entity()
|
||||
{
|
||||
return entity(nextId++);
|
||||
}
|
||||
entity lookup(const char *)
|
||||
{
|
||||
return entity();
|
||||
}
|
||||
|
||||
static world &get()
|
||||
{
|
||||
static world w;
|
||||
return w;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t nextId = 1000;
|
||||
};
|
||||
|
||||
} // namespace flecs
|
||||
|
||||
// Forward declare the registration function
|
||||
namespace editScene
|
||||
{
|
||||
void registerLuaEntityApi(lua_State *L);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testCount = 0;
|
||||
static int passCount = 0;
|
||||
|
||||
#define TEST(name) \
|
||||
do { \
|
||||
testCount++; \
|
||||
printf(" TEST %d: %s ... ", testCount, name); \
|
||||
} while (0)
|
||||
|
||||
#define PASS() \
|
||||
do { \
|
||||
passCount++; \
|
||||
printf("PASS\n"); \
|
||||
} while (0)
|
||||
|
||||
#define FAIL(msg) \
|
||||
do { \
|
||||
printf("FAIL: %s\n", msg); \
|
||||
return 1; \
|
||||
} while (0)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: run a Lua string and check for errors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static bool runLua(lua_State *L, const char *code)
|
||||
{
|
||||
if (luaL_dostring(L, code) != LUA_OK) {
|
||||
fprintf(stderr, "Lua error: %s\n", lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 1: Create entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testCreateEntity(lua_State *L)
|
||||
{
|
||||
TEST("create entity returns integer ID");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local id = ecs.create_entity();"
|
||||
"assert(type(id) == 'number', 'expected number, got ' .. type(id));"
|
||||
"assert(id > 0, 'expected positive ID, got ' .. tostring(id))");
|
||||
if (!ok)
|
||||
FAIL("create entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 2: Entity exists
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEntityExists(lua_State *L)
|
||||
{
|
||||
TEST("entity_exists returns correct values");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local id = ecs.create_entity();"
|
||||
"assert(ecs.entity_exists(id) == true, 'entity should exist');"
|
||||
"assert(ecs.entity_exists(999999) == false, 'fake entity should not exist')");
|
||||
if (!ok)
|
||||
FAIL("entity exists assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 3: Destroy entity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testDestroyEntity(lua_State *L)
|
||||
{
|
||||
TEST("destroy entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local id = ecs.create_entity();"
|
||||
"assert(ecs.entity_exists(id) == true, 'entity should exist before destroy');"
|
||||
"ecs.destroy_entity(id);"
|
||||
"assert(ecs.entity_exists(id) == false, 'entity should not exist after destroy')");
|
||||
if (!ok)
|
||||
FAIL("destroy entity assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 4: Set and get entity name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEntityName(lua_State *L)
|
||||
{
|
||||
TEST("set and get entity name");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local id = ecs.create_entity();"
|
||||
"ecs.set_entity_name(id, 'test_hero');"
|
||||
"local name = ecs.get_entity_name(id);"
|
||||
"assert(name == 'test_hero', 'expected test_hero, got ' .. tostring(name))");
|
||||
if (!ok)
|
||||
FAIL("entity name assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 5: Get entity by name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testGetEntityByName(lua_State *L)
|
||||
{
|
||||
TEST("get entity by name");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local id = ecs.create_entity();"
|
||||
"ecs.set_entity_name(id, 'findable_entity');"
|
||||
"local found = ecs.get_entity_by_name('findable_entity');"
|
||||
"assert(found == id, 'expected same ID, got ' .. tostring(found));"
|
||||
"local not_found = ecs.get_entity_by_name('nonexistent');"
|
||||
"assert(not_found == nil, 'expected nil for nonexistent')");
|
||||
if (!ok)
|
||||
FAIL("get entity by name assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: Parent and children hierarchy
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testHierarchy(lua_State *L)
|
||||
{
|
||||
TEST("parent and children hierarchy");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local parent = ecs.create_entity();"
|
||||
"local child = ecs.create_entity();"
|
||||
"ecs.set_entity_name(parent, 'parent_entity');"
|
||||
"ecs.set_entity_name(child, 'child_entity');"
|
||||
"local p = ecs.parent(child);"
|
||||
"assert(p == nil, 'child should have no parent initially');"
|
||||
"local kids = ecs.children(parent);"
|
||||
"assert(type(kids) == 'table', 'children should return a table');"
|
||||
"assert(#kids == 0, 'parent should have no children initially')");
|
||||
if (!ok)
|
||||
FAIL("hierarchy assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: Multiple entity creation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testMultipleEntities(lua_State *L)
|
||||
{
|
||||
TEST("create multiple entities with unique IDs");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local ids = {};"
|
||||
"for i = 1, 5 do"
|
||||
" ids[i] = ecs.create_entity();"
|
||||
"end;"
|
||||
"for i = 1, 5 do"
|
||||
" for j = i+1, 5 do"
|
||||
" assert(ids[i] ~= ids[j], 'IDs should be unique');"
|
||||
" end;"
|
||||
"end;"
|
||||
"for i = 1, 5 do"
|
||||
" assert(ecs.entity_exists(ids[i]) == true, 'entity should exist');"
|
||||
"end");
|
||||
if (!ok)
|
||||
FAIL("multiple entities assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: Destroy and recreate (ID reuse)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testDestroyRecreate(lua_State *L)
|
||||
{
|
||||
TEST("destroy and recreate entity");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local id1 = ecs.create_entity();"
|
||||
"ecs.destroy_entity(id1);"
|
||||
"assert(ecs.entity_exists(id1) == false, 'destroyed entity should not exist');"
|
||||
"local id2 = ecs.create_entity();"
|
||||
"assert(ecs.entity_exists(id2) == true, 'new entity should exist');"
|
||||
"assert(id2 ~= id1, 'new entity should have different ID')");
|
||||
if (!ok)
|
||||
FAIL("destroy recreate assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Entity Lua API Tests\n");
|
||||
printf("====================\n\n");
|
||||
|
||||
// Create Lua state
|
||||
lua_State *L = luaL_newstate();
|
||||
if (!L) {
|
||||
fprintf(stderr, "Failed to create Lua state\n");
|
||||
return 1;
|
||||
}
|
||||
luaL_openlibs(L);
|
||||
|
||||
// Register the entity API
|
||||
editScene::registerLuaEntityApi(L);
|
||||
|
||||
// Run tests
|
||||
int failures = 0;
|
||||
failures += testCreateEntity(L);
|
||||
failures += testEntityExists(L);
|
||||
failures += testDestroyEntity(L);
|
||||
failures += testEntityName(L);
|
||||
failures += testGetEntityByName(L);
|
||||
failures += testHierarchy(L);
|
||||
failures += testMultipleEntities(L);
|
||||
failures += testDestroyRecreate(L);
|
||||
|
||||
// Cleanup
|
||||
lua_close(L);
|
||||
|
||||
printf("\nResults: %d/%d passed, %d failed\n", passCount, testCount,
|
||||
failures);
|
||||
|
||||
return failures > 0 ? 1 : 0;
|
||||
}
|
||||
375
src/features/editScene/tests/event_lua_test.cpp
Normal file
375
src/features/editScene/tests/event_lua_test.cpp
Normal file
@@ -0,0 +1,375 @@
|
||||
/**
|
||||
* @file event_lua_test.cpp
|
||||
* @brief Standalone test for the Lua Event API.
|
||||
*
|
||||
* Tests event subscription, unsubscription, and sending functions
|
||||
* exposed via the ecs.* Lua API.
|
||||
*
|
||||
* Build with:
|
||||
* g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
|
||||
* event_lua_test.cpp \
|
||||
* ../lua/LuaEventApi.cpp \
|
||||
* ../lua/LuaEntityApi.cpp \
|
||||
* ../systems/EventBus.cpp \
|
||||
* ../components/GoapBlackboard.cpp \
|
||||
* ../../lua/lua-5.4.8/src/liblua.a \
|
||||
* -o event_lua_test -lm
|
||||
*
|
||||
* Or via CMake (see CMakeLists.txt in this directory).
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Ogre stub (provides Ogre::String, Ogre::Vector3, Ogre::LogManager)
|
||||
#include "ogre_stub.h"
|
||||
|
||||
// Include Lua
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
}
|
||||
|
||||
// Include the components we need
|
||||
#include "../systems/EventBus.hpp"
|
||||
#include "../components/GoapBlackboard.hpp"
|
||||
|
||||
// Forward declare the registration function
|
||||
namespace editScene
|
||||
{
|
||||
void registerLuaEventApi(lua_State *L);
|
||||
void registerLuaEntityApi(lua_State *L);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testCount = 0;
|
||||
static int passCount = 0;
|
||||
|
||||
#define TEST(name) \
|
||||
do { \
|
||||
testCount++; \
|
||||
printf(" TEST %d: %s ... ", testCount, name); \
|
||||
} while (0)
|
||||
|
||||
#define PASS() \
|
||||
do { \
|
||||
passCount++; \
|
||||
printf("PASS\n"); \
|
||||
} while (0)
|
||||
|
||||
#define FAIL(msg) \
|
||||
do { \
|
||||
printf("FAIL: %s\n", msg); \
|
||||
return 1; \
|
||||
} while (0)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: run a Lua string and check for errors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static bool runLua(lua_State *L, const char *code)
|
||||
{
|
||||
if (luaL_dostring(L, code) != LUA_OK) {
|
||||
fprintf(stderr, "Lua error: %s\n", lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 1: Send a simple event
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSendSimpleEvent(lua_State *L)
|
||||
{
|
||||
TEST("send a simple event");
|
||||
|
||||
bool ok = runLua(L, "ecs.send_event('test_event')");
|
||||
if (!ok)
|
||||
FAIL("failed to send simple event");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 2: Subscribe and receive an event
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSubscribeAndReceive(lua_State *L)
|
||||
{
|
||||
TEST("subscribe and receive an event");
|
||||
|
||||
// Track whether the callback was called
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local received = false;"
|
||||
"local sub_id = ecs.subscribe_event('hello', function(event, params)"
|
||||
" received = true;"
|
||||
"end);"
|
||||
"assert(sub_id ~= nil, 'subscription ID should not be nil');"
|
||||
"assert(type(sub_id) == 'number', 'subscription ID should be a number');"
|
||||
"ecs.send_event('hello');"
|
||||
"assert(received == true, 'callback should have been called')");
|
||||
if (!ok)
|
||||
FAIL("subscribe and receive assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 3: Subscribe with event name and params
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testSubscribeWithParams(lua_State *L)
|
||||
{
|
||||
TEST("subscribe with event name and params");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local received_event = nil;"
|
||||
"local sub_id = ecs.subscribe_event('collision', function(event, params)"
|
||||
" received_event = event;"
|
||||
"end);"
|
||||
"ecs.send_event('collision', { values = { damage = 10 } });"
|
||||
"assert(received_event == 'collision', 'expected collision event')");
|
||||
if (!ok)
|
||||
FAIL("subscribe with params assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 4: Unsubscribe from an event
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testUnsubscribe(lua_State *L)
|
||||
{
|
||||
TEST("unsubscribe from an event");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local call_count = 0;"
|
||||
"local sub_id = ecs.subscribe_event('temp', function(event, params)"
|
||||
" call_count = call_count + 1;"
|
||||
"end);"
|
||||
"ecs.send_event('temp');"
|
||||
"assert(call_count == 1, 'should have been called once');"
|
||||
"ecs.unsubscribe_event(sub_id);"
|
||||
"ecs.send_event('temp');"
|
||||
"assert(call_count == 1, 'should not have been called after unsubscribe')");
|
||||
if (!ok)
|
||||
FAIL("unsubscribe assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 5: Multiple subscribers to same event
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testMultipleSubscribers(lua_State *L)
|
||||
{
|
||||
TEST("multiple subscribers to same event");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local count1 = 0; local count2 = 0;"
|
||||
"local s1 = ecs.subscribe_event('multi', function() count1 = count1 + 1; end);"
|
||||
"local s2 = ecs.subscribe_event('multi', function() count2 = count2 + 1; end);"
|
||||
"ecs.send_event('multi');"
|
||||
"assert(count1 == 1, 'subscriber 1 should have been called');"
|
||||
"assert(count2 == 1, 'subscriber 2 should have been called');"
|
||||
"ecs.send_event('multi');"
|
||||
"assert(count1 == 2, 'subscriber 1 should have been called twice');"
|
||||
"assert(count2 == 2, 'subscriber 2 should have been called twice')");
|
||||
if (!ok)
|
||||
FAIL("multiple subscribers assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: Send event with blackboard params
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEventWithBlackboardParams(lua_State *L)
|
||||
{
|
||||
TEST("send event with blackboard params");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local result = nil;"
|
||||
"ecs.subscribe_event('data_event', function(event, params)"
|
||||
" result = params;"
|
||||
"end);"
|
||||
"ecs.send_event('data_event', {"
|
||||
" values = { score = 42, level = 5 },"
|
||||
" floatValues = { speed = 1.5 },"
|
||||
" stringValues = { name = 'hero' }"
|
||||
"});"
|
||||
"assert(result ~= nil, 'params should not be nil');"
|
||||
"assert(result.values.score == 42, 'expected score 42');"
|
||||
"assert(result.values.level == 5, 'expected level 5');"
|
||||
"assert(result.floatValues.speed == 1.5, 'expected speed 1.5');"
|
||||
"assert(result.stringValues.name == 'hero', 'expected name hero')");
|
||||
if (!ok)
|
||||
FAIL("event with blackboard params assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: Send event with vec3 params
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEventWithVec3Params(lua_State *L)
|
||||
{
|
||||
TEST("send event with vec3 params");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local result = nil;"
|
||||
"ecs.subscribe_event('move', function(event, params)"
|
||||
" result = params;"
|
||||
"end);"
|
||||
"ecs.send_event('move', {"
|
||||
" vec3Values = { position = { 10, 20, 30 }, velocity = { 1, 0, 0 } }"
|
||||
"});"
|
||||
"assert(result ~= nil, 'params should not be nil');"
|
||||
"assert(result.vec3Values.position[1] == 10, 'expected pos.x=10');"
|
||||
"assert(result.vec3Values.position[2] == 20, 'expected pos.y=20');"
|
||||
"assert(result.vec3Values.position[3] == 30, 'expected pos.z=30');"
|
||||
"assert(result.vec3Values.velocity[1] == 1, 'expected vel.x=1')");
|
||||
if (!ok)
|
||||
FAIL("event with vec3 params assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: Send event with bits and mask
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testEventWithBits(lua_State *L)
|
||||
{
|
||||
TEST("send event with bits and mask");
|
||||
|
||||
bool ok = runLua(
|
||||
L, "local result = nil;"
|
||||
"ecs.subscribe_event('flag_event', function(event, params)"
|
||||
" result = params;"
|
||||
"end);"
|
||||
"ecs.send_event('flag_event', {"
|
||||
" bits = 5,"
|
||||
" mask = 7"
|
||||
"});"
|
||||
"assert(result ~= nil, 'params should not be nil');"
|
||||
"assert(result.bits == 5, 'expected bits=5');"
|
||||
"assert(result.mask == 7, 'expected mask=7')");
|
||||
if (!ok)
|
||||
FAIL("event with bits assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 9: Multiple events with different names
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testMultipleEvents(lua_State *L)
|
||||
{
|
||||
TEST("multiple events with different names");
|
||||
|
||||
bool ok = runLua(
|
||||
L,
|
||||
"local events = {};"
|
||||
"ecs.subscribe_event('event_a', function() table.insert(events, 'a'); end);"
|
||||
"ecs.subscribe_event('event_b', function() table.insert(events, 'b'); end);"
|
||||
"ecs.send_event('event_a');"
|
||||
"ecs.send_event('event_b');"
|
||||
"ecs.send_event('event_a');"
|
||||
"assert(#events == 3, 'expected 3 events, got ' .. #events);"
|
||||
"assert(events[1] == 'a', 'expected a');"
|
||||
"assert(events[2] == 'b', 'expected b');"
|
||||
"assert(events[3] == 'a', 'expected a')");
|
||||
if (!ok)
|
||||
FAIL("multiple events assertion failed");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 10: Unsubscribe invalid ID (should not crash)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int testUnsubscribeInvalid(lua_State *L)
|
||||
{
|
||||
TEST("unsubscribe invalid ID (should not crash)");
|
||||
|
||||
bool ok = runLua(L, "ecs.unsubscribe_event(99999);");
|
||||
if (!ok)
|
||||
FAIL("unsubscribe invalid ID should not error");
|
||||
|
||||
PASS();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Event Lua API Tests\n");
|
||||
printf("===================\n\n");
|
||||
|
||||
// Create Lua state
|
||||
lua_State *L = luaL_newstate();
|
||||
if (!L) {
|
||||
fprintf(stderr, "Failed to create Lua state\n");
|
||||
return 1;
|
||||
}
|
||||
luaL_openlibs(L);
|
||||
|
||||
// Register the entity and event APIs
|
||||
editScene::registerLuaEntityApi(L);
|
||||
editScene::registerLuaEventApi(L);
|
||||
|
||||
// Run tests
|
||||
int failures = 0;
|
||||
failures += testSendSimpleEvent(L);
|
||||
failures += testSubscribeAndReceive(L);
|
||||
failures += testSubscribeWithParams(L);
|
||||
failures += testUnsubscribe(L);
|
||||
failures += testMultipleSubscribers(L);
|
||||
failures += testEventWithBlackboardParams(L);
|
||||
failures += testEventWithVec3Params(L);
|
||||
failures += testEventWithBits(L);
|
||||
failures += testMultipleEvents(L);
|
||||
failures += testUnsubscribeInvalid(L);
|
||||
|
||||
// Cleanup
|
||||
lua_close(L);
|
||||
|
||||
printf("\nResults: %d/%d passed, %d failed\n", passCount, testCount,
|
||||
failures);
|
||||
|
||||
return failures > 0 ? 1 : 0;
|
||||
}
|
||||
576
src/features/editScene/tests/lua_test_stubs.cpp
Normal file
576
src/features/editScene/tests/lua_test_stubs.cpp
Normal file
@@ -0,0 +1,576 @@
|
||||
/**
|
||||
* @file lua_test_stubs.cpp
|
||||
* @brief Stub implementations of Lua API registration functions for standalone tests.
|
||||
*
|
||||
* These stubs provide minimal implementations that work with the
|
||||
* flecs stubs and ogre_stub.h defined in the test files.
|
||||
* They are used instead of the real Lua API .cpp files which
|
||||
* require the full OGRE SDK.
|
||||
*
|
||||
* The stubs maintain state (entity IDs, names, components) so that
|
||||
* the tests can verify correct behavior.
|
||||
*/
|
||||
|
||||
#include "ogre_stub.h"
|
||||
#include <lua.hpp>
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared component storage (used by both entity and component API stubs)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
// A value can be a number, string, boolean, array of numbers, array of strings,
|
||||
// or a nested table (map of string->value)
|
||||
struct ComponentFieldValue {
|
||||
enum Type { NIL, NUMBER, STRING, BOOLEAN, NUM_ARRAY, STR_ARRAY, TABLE };
|
||||
Type type = NIL;
|
||||
double numVal = 0;
|
||||
std::string strVal;
|
||||
bool boolVal = false;
|
||||
std::vector<double> numArr;
|
||||
std::vector<std::string> strArr;
|
||||
std::map<std::string, ComponentFieldValue> tableVal;
|
||||
};
|
||||
|
||||
using ComponentData = std::unordered_map<std::string, ComponentFieldValue>;
|
||||
std::unordered_map<int, std::unordered_map<std::string, ComponentData> >
|
||||
s_components;
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stub: LuaEntityApi
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
// Entity state for stubs
|
||||
struct EntityState {
|
||||
bool alive;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
static std::unordered_map<int, EntityState> s_entities;
|
||||
static int s_nextId = 1000;
|
||||
|
||||
static int createEntity()
|
||||
{
|
||||
int id = s_nextId++;
|
||||
s_entities[id] = { true, "" };
|
||||
// Auto-add EditorMarker component (matching real behavior)
|
||||
s_components[id]["EditorMarker"];
|
||||
return id;
|
||||
}
|
||||
|
||||
static void destroyEntity(int id)
|
||||
{
|
||||
auto it = s_entities.find(id);
|
||||
if (it != s_entities.end())
|
||||
it->second.alive = false;
|
||||
}
|
||||
|
||||
static bool entityExists(int id)
|
||||
{
|
||||
auto it = s_entities.find(id);
|
||||
return it != s_entities.end() && it->second.alive;
|
||||
}
|
||||
|
||||
static void setEntityName(int id, const std::string &name)
|
||||
{
|
||||
auto it = s_entities.find(id);
|
||||
if (it != s_entities.end())
|
||||
it->second.name = name;
|
||||
}
|
||||
|
||||
static std::string getEntityName(int id)
|
||||
{
|
||||
auto it = s_entities.find(id);
|
||||
if (it != s_entities.end())
|
||||
return it->second.name;
|
||||
return "";
|
||||
}
|
||||
|
||||
static int findEntityByName(const std::string &name)
|
||||
{
|
||||
for (auto &[id, state] : s_entities) {
|
||||
if (state.alive && state.name == name)
|
||||
return id;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void registerLuaEntityApi(lua_State *L)
|
||||
{
|
||||
// Create the "ecs" global table
|
||||
lua_newtable(L);
|
||||
|
||||
// create_entity
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int id = createEntity();
|
||||
lua_pushinteger(L, id);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "create_entity");
|
||||
|
||||
// destroy_entity
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int id = lua_tointeger(L, 1);
|
||||
destroyEntity(id);
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "destroy_entity");
|
||||
|
||||
// entity_exists
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int id = lua_tointeger(L, 1);
|
||||
lua_pushboolean(L, entityExists(id) ? 1 : 0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "entity_exists");
|
||||
|
||||
// get_player_entity
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int id = findEntityByName("player");
|
||||
if (id >= 0) {
|
||||
lua_pushinteger(L, id);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_player_entity");
|
||||
|
||||
// get_entity_by_name
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
const char *name = lua_tostring(L, 1);
|
||||
int id = findEntityByName(name ? name : "");
|
||||
if (id >= 0) {
|
||||
lua_pushinteger(L, id);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_entity_by_name");
|
||||
|
||||
// set_entity_name
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int id = lua_tointeger(L, 1);
|
||||
const char *name = lua_tostring(L, 2);
|
||||
setEntityName(id, name ? name : "");
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "set_entity_name");
|
||||
|
||||
// get_entity_name
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int id = lua_tointeger(L, 1);
|
||||
std::string name = getEntityName(id);
|
||||
if (!name.empty()) {
|
||||
lua_pushstring(L, name.c_str());
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_entity_name");
|
||||
|
||||
// parent
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "parent");
|
||||
|
||||
// children
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
lua_newtable(L);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "children");
|
||||
|
||||
lua_setglobal(L, "ecs");
|
||||
}
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stub: LuaComponentApi
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
static ComponentData &getOrCreateComponent(int entityId,
|
||||
const std::string &compName)
|
||||
{
|
||||
return s_components[entityId][compName];
|
||||
}
|
||||
|
||||
static bool hasComponent(int entityId, const std::string &compName)
|
||||
{
|
||||
auto eit = s_components.find(entityId);
|
||||
if (eit == s_components.end())
|
||||
return false;
|
||||
return eit->second.find(compName) != eit->second.end();
|
||||
}
|
||||
|
||||
static void removeComponent(int entityId, const std::string &compName)
|
||||
{
|
||||
auto eit = s_components.find(entityId);
|
||||
if (eit != s_components.end())
|
||||
eit->second.erase(compName);
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
static void pushFieldValueToLua(lua_State *L, const ComponentFieldValue &fv);
|
||||
|
||||
static void pushFieldValueToLua(lua_State *L, const ComponentFieldValue &fv)
|
||||
{
|
||||
switch (fv.type) {
|
||||
case ComponentFieldValue::NIL:
|
||||
lua_pushnil(L);
|
||||
break;
|
||||
case ComponentFieldValue::NUMBER:
|
||||
lua_pushnumber(L, fv.numVal);
|
||||
break;
|
||||
case ComponentFieldValue::STRING:
|
||||
lua_pushstring(L, fv.strVal.c_str());
|
||||
break;
|
||||
case ComponentFieldValue::BOOLEAN:
|
||||
lua_pushboolean(L, fv.boolVal ? 1 : 0);
|
||||
break;
|
||||
case ComponentFieldValue::NUM_ARRAY: {
|
||||
lua_newtable(L);
|
||||
for (size_t i = 0; i < fv.numArr.size(); i++) {
|
||||
lua_pushnumber(L, fv.numArr[i]);
|
||||
lua_rawseti(L, -2, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ComponentFieldValue::STR_ARRAY: {
|
||||
lua_newtable(L);
|
||||
for (size_t i = 0; i < fv.strArr.size(); i++) {
|
||||
lua_pushstring(L, fv.strArr[i].c_str());
|
||||
lua_rawseti(L, -2, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ComponentFieldValue::TABLE: {
|
||||
lua_newtable(L);
|
||||
for (auto &[k, v] : fv.tableVal) {
|
||||
pushFieldValueToLua(L, v);
|
||||
lua_setfield(L, -2, k.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
static ComponentFieldValue readLuaValue(lua_State *L, int idx);
|
||||
|
||||
static ComponentFieldValue readLuaValue(lua_State *L, int idx)
|
||||
{
|
||||
ComponentFieldValue fv;
|
||||
// Convert to absolute index to be safe with stack changes
|
||||
int absIdx = lua_absindex(L, idx);
|
||||
int type = lua_type(L, absIdx);
|
||||
if (type == LUA_TNIL) {
|
||||
fv.type = ComponentFieldValue::NIL;
|
||||
} else if (type == LUA_TNUMBER) {
|
||||
fv.type = ComponentFieldValue::NUMBER;
|
||||
fv.numVal = lua_tonumber(L, absIdx);
|
||||
} else if (type == LUA_TSTRING) {
|
||||
fv.type = ComponentFieldValue::STRING;
|
||||
fv.strVal = lua_tostring(L, absIdx);
|
||||
} else if (type == LUA_TBOOLEAN) {
|
||||
fv.type = ComponentFieldValue::BOOLEAN;
|
||||
fv.boolVal = lua_toboolean(L, absIdx) != 0;
|
||||
} else if (type == LUA_TTABLE) {
|
||||
// Check if it's an array (all integer keys) or a map (string keys)
|
||||
bool isArray = true;
|
||||
bool isStringArray = false;
|
||||
bool isNumArray = false;
|
||||
int maxKey = 0;
|
||||
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, absIdx) != 0) {
|
||||
if (lua_type(L, -2) == LUA_TNUMBER) {
|
||||
int k = lua_tointeger(L, -2);
|
||||
if (k > maxKey)
|
||||
maxKey = k;
|
||||
if (lua_type(L, -1) == LUA_TSTRING)
|
||||
isStringArray = true;
|
||||
else if (lua_type(L, -1) == LUA_TNUMBER)
|
||||
isNumArray = true;
|
||||
} else {
|
||||
isArray = false;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
if (isArray && maxKey > 0) {
|
||||
if (isStringArray) {
|
||||
fv.type = ComponentFieldValue::STR_ARRAY;
|
||||
for (int i = 1; i <= maxKey; i++) {
|
||||
lua_rawgeti(L, absIdx, i);
|
||||
if (lua_type(L, -1) == LUA_TSTRING)
|
||||
fv.strArr.push_back(
|
||||
lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else if (isNumArray) {
|
||||
fv.type = ComponentFieldValue::NUM_ARRAY;
|
||||
for (int i = 1; i <= maxKey; i++) {
|
||||
lua_rawgeti(L, absIdx, i);
|
||||
if (lua_type(L, -1) == LUA_TNUMBER)
|
||||
fv.numArr.push_back(
|
||||
lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fv.type = ComponentFieldValue::TABLE;
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, absIdx) != 0) {
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
const char *key = lua_tostring(L, -2);
|
||||
if (key)
|
||||
fv.tableVal[key] =
|
||||
readLuaValue(L, -1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return fv;
|
||||
}
|
||||
|
||||
void registerLuaComponentApi(lua_State *L)
|
||||
{
|
||||
// Get or create the "ecs" global table
|
||||
lua_getglobal(L, "ecs");
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
lua_newtable(L);
|
||||
}
|
||||
|
||||
// has_component
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int entityId = lua_tointeger(L, 1);
|
||||
const char *compName = lua_tostring(L, 2);
|
||||
lua_pushboolean(L, hasComponent(entityId,
|
||||
compName ? compName : "") ?
|
||||
1 :
|
||||
0);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "has_component");
|
||||
|
||||
// add_component
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int entityId = lua_tointeger(L, 1);
|
||||
const char *compName = lua_tostring(L, 2);
|
||||
if (compName)
|
||||
getOrCreateComponent(entityId, compName);
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "add_component");
|
||||
|
||||
// remove_component
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int entityId = lua_tointeger(L, 1);
|
||||
const char *compName = lua_tostring(L, 2);
|
||||
if (compName)
|
||||
removeComponent(entityId, compName);
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "remove_component");
|
||||
|
||||
// get_component
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int entityId = lua_tointeger(L, 1);
|
||||
const char *compName = lua_tostring(L, 2);
|
||||
if (!compName || !hasComponent(entityId, compName)) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
ComponentData &cd = s_components[entityId][compName];
|
||||
lua_newtable(L);
|
||||
for (auto &[k, v] : cd) {
|
||||
pushFieldValueToLua(L, v);
|
||||
lua_setfield(L, -2, k.c_str());
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_component");
|
||||
|
||||
// set_component
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int entityId = lua_tointeger(L, 1);
|
||||
const char *compName = lua_tostring(L, 2);
|
||||
if (!compName)
|
||||
return 0;
|
||||
ComponentData &cd = getOrCreateComponent(entityId, compName);
|
||||
if (lua_istable(L, 3)) {
|
||||
cd.clear();
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, 3) != 0) {
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
const char *key = lua_tostring(L, -2);
|
||||
if (key)
|
||||
cd[key] = readLuaValue(L, -1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "set_component");
|
||||
|
||||
// get_field
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int entityId = lua_tointeger(L, 1);
|
||||
const char *compName = lua_tostring(L, 2);
|
||||
const char *fieldName = lua_tostring(L, 3);
|
||||
if (!compName || !fieldName ||
|
||||
!hasComponent(entityId, compName)) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
ComponentData &cd = s_components[entityId][compName];
|
||||
auto it = cd.find(fieldName);
|
||||
if (it != cd.end()) {
|
||||
pushFieldValueToLua(L, it->second);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get_field");
|
||||
|
||||
// set_field
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int entityId = lua_tointeger(L, 1);
|
||||
const char *compName = lua_tostring(L, 2);
|
||||
const char *fieldName = lua_tostring(L, 3);
|
||||
if (!compName || !fieldName)
|
||||
return 0;
|
||||
ComponentData &cd = getOrCreateComponent(entityId, compName);
|
||||
cd[fieldName] = readLuaValue(L, 4);
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "set_field");
|
||||
|
||||
lua_setglobal(L, "ecs");
|
||||
}
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stub: LuaEventApi
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace editScene
|
||||
{
|
||||
|
||||
// Event subscription storage
|
||||
struct EventSubscription {
|
||||
int id;
|
||||
std::string eventName;
|
||||
int callbackRef; // Lua registry reference
|
||||
};
|
||||
|
||||
static std::vector<EventSubscription> s_subscriptions;
|
||||
static int s_nextSubId = 1;
|
||||
|
||||
void registerLuaEventApi(lua_State *L)
|
||||
{
|
||||
// Get or create the "ecs" global table
|
||||
lua_getglobal(L, "ecs");
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
lua_newtable(L);
|
||||
}
|
||||
|
||||
// subscribe_event
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
const char *eventName = lua_tostring(L, 1);
|
||||
if (!eventName || !lua_isfunction(L, 2)) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
// Store callback in Lua registry
|
||||
lua_pushvalue(L, 2);
|
||||
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
|
||||
int subId = s_nextSubId++;
|
||||
s_subscriptions.push_back({ subId, eventName, ref });
|
||||
lua_pushinteger(L, subId);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "subscribe_event");
|
||||
|
||||
// unsubscribe_event
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int subId = lua_tointeger(L, 1);
|
||||
for (auto it = s_subscriptions.begin();
|
||||
it != s_subscriptions.end(); ++it) {
|
||||
if (it->id == subId) {
|
||||
luaL_unref(L, LUA_REGISTRYINDEX,
|
||||
it->callbackRef);
|
||||
s_subscriptions.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "unsubscribe_event");
|
||||
|
||||
// send_event
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
const char *eventName = lua_tostring(L, 1);
|
||||
if (!eventName)
|
||||
return 0;
|
||||
|
||||
// Call all matching subscriptions
|
||||
for (auto &sub : s_subscriptions) {
|
||||
if (sub.eventName == eventName) {
|
||||
// Push callback
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX,
|
||||
sub.callbackRef);
|
||||
// Push event name
|
||||
lua_pushstring(L, eventName);
|
||||
// Push params (table or nil)
|
||||
if (lua_istable(L, 2)) {
|
||||
lua_pushvalue(L, 2);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
// Call callback(event, params)
|
||||
if (lua_pcall(L, 2, 0, 0) != LUA_OK) {
|
||||
fprintf(stderr,
|
||||
"Event callback error: %s\n",
|
||||
lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "send_event");
|
||||
|
||||
lua_setglobal(L, "ecs");
|
||||
}
|
||||
|
||||
} // namespace editScene
|
||||
@@ -72,6 +72,17 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// OgreAssert macro (used by LuaEntityApi.hpp)
|
||||
#ifndef OgreAssert
|
||||
#define OgreAssert(expr, msg) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "OgreAssert failed: %s\n", msg); \
|
||||
assert(expr); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
} // namespace Ogre
|
||||
|
||||
#endif // OGRE_STUB_H
|
||||
|
||||
Reference in New Issue
Block a user