Compare commits

..

76 Commits

Author SHA1 Message Date
c44936d1fe adapt config 2026-06-14 14:21:24 +02:00
68c1345660 recipe iteration 2026-06-14 14:06:12 +02:00
dbf334c829 add python script to verify module constraints against hulls 2026-06-14 14:06:11 +02:00
f225c1330e refinement 2026-06-14 14:06:11 +02:00
fba98c928f design config 2026-06-14 14:06:10 +02:00
282ace4c11 fix bug where splitters reduce belt throughput, even if one side is blocked 2026-06-14 14:03:50 +02:00
1ea1cc59fb show threat rate in debug output 2026-06-14 13:39:10 +02:00
123c544423 move ui panels to the right 2026-06-14 13:07:25 +02:00
10c5ad678f derive threat cost dynamically 2026-06-13 22:47:46 +02:00
3716c2b734 show implicitly unlocked items in schematic unlock dialog 2026-06-13 18:19:25 +02:00
5317f35198 switch to using own event system 2026-06-13 17:52:22 +02:00
ed17664ef1 fix bug where game simulation continues while dialog is shown 2026-06-13 14:31:03 +02:00
49f7129bd5 schematic selection dialog 2026-06-13 14:19:51 +02:00
1641189b75 explicit recipe unlocking 2026-06-12 17:15:06 +02:00
54a6056b77 implicit item locking 2026-06-12 16:14:21 +02:00
69b35d2bfc fix config 2026-06-10 22:37:46 +02:00
af96b95f61 allow to unlock modules when destroying defence stations 2026-06-10 22:37:38 +02:00
aad094f842 allow to configure when which schematic gets unlockable 2026-06-10 21:09:03 +02:00
26857e8414 throw if modules are referenced that don't exist in config 2026-06-09 23:34:30 +02:00
510e37c37b fix issue where upgrade modules are not working properly 2026-06-09 23:34:29 +02:00
121cd5407f add more modules 2026-06-09 23:31:58 +02:00
7c663e29a6 fix threat accumulation config 2026-06-09 21:46:01 +02:00
c64d31fa46 pause threat accumulation during quiet windows 2026-06-07 23:21:22 +02:00
f097e9a25f add live ship stats panel 2026-06-07 22:06:37 +02:00
37a70ea321 add ship stats panel to ship layout dialog 2026-06-06 22:45:50 +02:00
8dad554800 show threat budget in debug text overlay 2026-06-06 21:07:18 +02:00
6b95619806 add units in config files 2026-06-06 20:46:36 +02:00
66cf9ae23a change 4x speed to 10x speed for testing 2026-06-06 12:01:41 +02:00
ef17b0ce42 reduce size of building surface masks 2026-06-06 12:00:38 +02:00
eeaa309c08 fix balancing config 2026-06-05 20:30:38 +02:00
7669245229 use meters in config 2026-06-05 20:09:20 +02:00
4e3e3ac715 replace combined stateUpdated signal with individual events 2026-06-05 18:27:46 +02:00
9677133c54 add event system and use it to propagate to print traces 2026-06-05 17:18:17 +02:00
abc261c03a add tracing for performance profiling 2026-06-05 16:38:36 +02:00
17e9913c98 wrap UI strings with tr() 2026-06-05 16:31:54 +02:00
900b5fdec1 remove documentation for already implemented change 2026-06-05 16:20:20 +02:00
3e19e44f24 store ship module layout in shipyard blueprint 2026-06-04 21:48:45 +02:00
42b51cc6f4 remove unnecessary modules for first playtest 2026-06-04 21:27:29 +02:00
15d8fa4f2c add boss wave counter and countdown to the title bar 2026-06-03 22:44:56 +02:00
b5185b0906 boss waves 2026-06-03 22:14:31 +02:00
457fc47c75 draw HP bars below ships 2026-06-03 20:47:48 +02:00
090dc64bc4 allow one target per repair tool module 2026-06-02 22:24:40 +02:00
64f7c9dcc1 add tests for salvager range and cooldown 2026-06-02 21:39:05 +02:00
f921f00a0d add collection rate for salvager modules and respect collection range of each of these modules 2026-06-02 21:28:31 +02:00
9d0a60a93b define ship roles via added modules and allow multiple weapons 2026-06-01 23:05:21 +02:00
f363f7a67c documentation 2026-05-27 17:03:13 +02:00
9e36c13635 fix tests 2026-05-25 21:13:16 +02:00
25ff3c56c5 move ecs related code to own folder 2026-05-25 09:10:54 +02:00
8ad7530740 fix ships to be drawn facing the correct direction in ArenaView 2026-05-25 07:56:03 +02:00
fa714335dc don't touch velocity in other systems and don't use snapTarget 2026-05-25 07:50:33 +02:00
0cd0529468 split MovementSystem to MovementIntentSystem and DynamicBodySystem 2026-05-25 07:25:54 +02:00
f5f4453e2c rename behavior components 2026-05-23 08:26:10 +02:00
b57299fd2a rename EntityId to BuildingId 2026-05-23 07:53:56 +02:00
dc4ebd5f2d fix crash 2026-05-22 22:11:56 +02:00
ea79d76953 unify Weapon and StationWeapon components 2026-05-22 22:06:30 +02:00
bd488db8ef remove unused building HP 2026-05-22 21:34:21 +02:00
4e3dc51981 fix stations were not drawn 2026-05-22 21:15:26 +02:00
9d20048705 cleanup 2026-05-22 20:45:10 +02:00
ca07cbaf0e switch to ECS architecture 2026-05-22 20:31:39 +02:00
c18c4e4804 implement claude feedback for EntityAdmin 2026-05-22 07:55:43 +02:00
cc2cca2442 add EntityAdmin wrapper 2026-05-22 06:34:57 +02:00
dc344df457 add entt dependency 2026-05-22 06:34:42 +02:00
452c26c8b3 split off MovementSystem and AiSystem from ShipSystem 2026-05-20 22:26:45 +02:00
34c6dea505 change to physics based ship movement 2026-05-20 19:44:46 +02:00
d397b9969a ship layout blueprints 2026-05-19 21:41:19 +02:00
d08bf5d37b implement ship modules 2026-05-18 08:54:26 +02:00
b59e392461 add requirements for ship modules 2026-05-17 22:51:44 +02:00
c0b6f8f778 balancing 2026-05-17 20:55:32 +02:00
3ef32ee6ba allow to inspect balancing arena 2026-05-04 09:18:39 +02:00
afd8cd28fa allow to re-load the config for the balancing app via button 2026-05-03 21:00:36 +02:00
6405ad6b3f allow to re-start arenas 2026-05-03 20:48:13 +02:00
55b42a03d9 balancing arenas can be started individually 2026-05-03 20:33:33 +02:00
426870158c also require defence station to be destroyed for balancing fight to finish 2026-05-03 11:27:16 +02:00
4eaae5d940 indicate balancing winner better 2026-05-03 11:23:24 +02:00
a4427f7f67 add balancing tool target 2026-05-03 11:17:54 +02:00
5153129909 add requirements for ship balancing tool 2026-05-03 10:35:46 +02:00
272 changed files with 40300 additions and 3882 deletions

View File

@@ -32,7 +32,6 @@ cost = 15
player_placeable = true
construction_time_seconds = 1
surface_mask = [
"AA",
"A>",
]
@@ -42,8 +41,8 @@ cost = 20
player_placeable = true
construction_time_seconds = 1
surface_mask = [
"AA ",
"AA>",
"AA",
" v",
]
[[building]]
@@ -52,9 +51,8 @@ cost = 35
player_placeable = true
construction_time_seconds = 1
surface_mask = [
"AAA ",
"AAA>",
"AAA ",
"AA ",
"AA>",
]
[[building]]

View File

@@ -0,0 +1,260 @@
# modules.toml
#
# First real-content iteration: module ids and surface masks are the designed
# content; stats, materials, and threat costs are placeholders until the
# recipe and balancing passes.
#
# Surface mask footprint ladder — footprints gate which hulls can mount a
# module, purely through geometry (see ships.toml for the matching hull
# grids):
#
# 1x1 laser_cannon_s, salvager, repair_tool fits every hull, incl. drones
# 1x2 maneuvering_thrusters, sensor_booster,
# armor_plates frigate and up
# 1x3 afterburner frigate and up (eats most of a frigate)
# L-shape weapon_stabilizer, weapon_primer,
# weapon_upgrade frigate and up
# 2x2 laser_cannon_m, drone_bay cruiser and up (no 2x2 area on s hulls)
# 3x3 laser_cannon_l battleship and up (no 3x3 area on m hulls)
# 2x6 drone_hangar carrier only
# -----------------------------------------------------------------------------
# Weapons
# -----------------------------------------------------------------------------
[[module]]
id = "laser_cannon_s"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "laser_cannon_s_module", amount = 1}]
player_production_level = 1
production_time_seconds = 0.5
fill_color = "#FF8040"
glyph = "Ls"
[module.weapon]
damage_formula = "2"
attack_range_m_formula = "50"
attack_rate_hz_formula = "2.0"
[[module]]
id = "laser_cannon_m"
unlock_at_station_level = -1
surface_mask = [
"OO",
"OO"]
materials = [{item = "laser_cannon_m_module", amount = 1}]
player_production_level = 1
production_time_seconds = 2
fill_color = "#FF8040"
glyph = "Lm"
[module.weapon]
damage_formula = "10"
attack_range_m_formula = "70"
attack_rate_hz_formula = "1.5"
[[module]]
id = "laser_cannon_l"
unlock_at_station_level = -1
surface_mask = [
"OOO",
"OOO",
"OOO"]
materials = [{item = "laser_cannon_l_module", amount = 1}]
player_production_level = 1
production_time_seconds = 8
fill_color = "#FF8040"
glyph = "Ll"
[module.weapon]
damage_formula = "40"
attack_range_m_formula = "100"
attack_rate_hz_formula = "0.8"
# -----------------------------------------------------------------------------
# Utility tools
# -----------------------------------------------------------------------------
[[module]]
id = "salvager"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "salvager_module", amount = 1}]
player_production_level = 1
production_time_seconds = 2
fill_color = "#AACC44"
glyph = "Sv"
[module.salvage]
collection_range_m_formula = "500"
cargo_capacity_formula = "10"
collection_rate_hz_formula = "0.5"
[[module]]
id = "repair_tool"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "repair_tool_module", amount = 1}]
player_production_level = 1
production_time_seconds = 2
fill_color = "#66CCFF"
glyph = "Rp"
[module.repair]
repair_rate_hz_formula = "5 + x"
repair_range_m_formula = "800"
# -----------------------------------------------------------------------------
# Propulsion
# -----------------------------------------------------------------------------
[[module]]
id = "afterburner"
unlock_at_station_level = -1
surface_mask = ["OOO"]
materials = [{item = "afterburner_module", amount = 1}]
player_production_level = 1
production_time_seconds = 2
fill_color = "#40A0FF"
glyph = "Ab"
[module.movement]
multiplied_speed_mps_formula = "1.6"
added_main_acceleration_mpss_formula = "60"
[[module]]
id = "maneuvering_thrusters"
unlock_at_station_level = -1
surface_mask = ["OO"]
materials = [{item = "maneuvering_thrusters_module", amount = 1}]
player_production_level = 1
production_time_seconds = 2
fill_color = "#40A0FF"
glyph = "Mt"
[module.movement]
multiplied_speed_mps_formula = "1.2"
added_maneuvering_acceleration_mpss_formula = "10"
# -----------------------------------------------------------------------------
# Defense & sensors
# -----------------------------------------------------------------------------
[[module]]
id = "armor_plates"
unlock_at_station_level = -1
surface_mask = ["OO"]
materials = [{item = "armor_plates_module", amount = 1}]
player_production_level = 1
production_time_seconds = 3
fill_color = "#808080"
glyph = "A"
[module.health]
added_hp_formula = "40"
[[module]]
id = "sensor_booster"
unlock_at_station_level = -1
surface_mask = ["OO"]
materials = [{item = "sensor_booster_module", amount = 1}]
player_production_level = 1
production_time_seconds = 2
fill_color = "#40A0FF"
glyph = "S"
[module.sensor]
added_sensor_range_m_formula = "50"
# -----------------------------------------------------------------------------
# Weapon modifiers
# -----------------------------------------------------------------------------
[[module]]
id = "weapon_upgrade"
unlock_at_station_level = -1
surface_mask = [
"OO",
"OX",
]
materials = [{item = "weapon_upgrade_module", amount = 1}]
player_production_level = 1
production_time_seconds = 4
fill_color = "#FF4040"
glyph = "Wu"
[module.weapon]
multiplied_damage_formula = "1.2"
[[module]]
id = "weapon_primer"
unlock_at_station_level = -1
surface_mask = [
"OO",
"OX",
]
materials = [{item = "weapon_primer_module", amount = 1}]
player_production_level = 1
production_time_seconds = 4
fill_color = "#FF4040"
glyph = "Wp"
[module.weapon]
multiplied_attack_rate_hz_formula = "1.2"
[[module]]
id = "weapon_stabilizer"
unlock_at_station_level = -1
surface_mask = [
"OO",
"OX",
]
materials = [{item = "weapon_stabilizer_module", amount = 1}]
player_production_level = 1
production_time_seconds = 4
fill_color = "#FF4040"
glyph = "Ws"
[module.weapon]
multiplied_attack_range_m_formula = "1.5"
multiplied_attack_rate_hz_formula = "0.8"
# -----------------------------------------------------------------------------
# Drone modules
#
# Footprint-only placeholders: the drone launching capability is not
# implemented yet, so these modules define no capability section.
# -----------------------------------------------------------------------------
[[module]]
id = "drone_bay"
unlock_at_station_level = -1
surface_mask = [
"OO",
"OO"]
materials = [{item = "drone_bay_module", amount = 1}]
player_production_level = 1
production_time_seconds = 5
fill_color = "#CC66FF"
glyph = "Db"
[[module]]
id = "drone_hangar"
unlock_at_station_level = -1
surface_mask = [
"OOOOOO",
"OOOOOO"]
materials = [{item = "drone_hangar_module", amount = 1}]
player_production_level = 1
production_time_seconds = 20
fill_color = "#9933CC"
glyph = "Dh"

View File

@@ -1,3 +1,29 @@
# recipes.toml
#
# First real-content iteration of the production tree. Quantities and
# durations are a first guess; the balancing pass will tune them and assign
# real unlock_at_station_level values (everything is unlocked for now so the
# full tree is testable).
#
# Input chain per game phase — each phase adds exactly one new base input:
#
# early iron_ore + copper_ore -> ingots -> copper_wire, steel_plate,
# circuit_board
# mid + titanium_ore -> titanium_frame; assembler-made
# mechanical_parts, targeting_unit,
# drive_unit
# late + advanced_alloy -> reinforced_plating, capital_core.
# advanced_alloy CANNOT be mined; it only
# comes from reprocessing salvaged scrap,
# so capital production requires combat.
#
# Run tools/verify_recipes.py after editing to check that every consumed
# item has a producer and every item has a visuals.toml entry.
# -----------------------------------------------------------------------------
# Mining (tier 0)
# -----------------------------------------------------------------------------
[[recipe]]
id = "mine_iron_ore"
building = "miner"
@@ -12,6 +38,18 @@ inputs = []
outputs = [{item = "copper_ore", amount = 1}]
duration_seconds = 1.5
# Titanium is the midgame ore: mined three times slower than iron.
[[recipe]]
id = "mine_titanium_ore"
building = "miner"
inputs = []
outputs = [{item = "titanium_ore", amount = 1}]
duration_seconds = 3.0
# -----------------------------------------------------------------------------
# Smelting (tier 1)
# -----------------------------------------------------------------------------
[[recipe]]
id = "iron_ingot"
building = "smelter"
@@ -27,19 +65,18 @@ outputs = [{item = "copper_ingot", amount = 1}]
duration_seconds = 2.5
[[recipe]]
id = "circuit_board"
building = "assembler"
inputs = [{item = "iron_ingot", amount = 3}, {item = "copper_ingot", amount = 2}]
outputs = [{item = "circuit_board", amount = 1}]
duration_seconds = 5.0
[[recipe]]
id = "building_blocks"
building = "assembler"
inputs = [{item = "iron_ingot", amount = 4}]
outputs = [{item = "building_block", amount = 10}]
id = "titanium_ingot"
building = "smelter"
inputs = [{item = "titanium_ore", amount = 3}]
outputs = [{item = "titanium_ingot", amount = 1}]
duration_seconds = 4.0
# -----------------------------------------------------------------------------
# Reprocessing
#
# The only source of advanced_alloy: salvaged scrap from destroyed ships.
# -----------------------------------------------------------------------------
[[recipe]]
id = "reprocessing_cycle"
building = "reprocessing_plant"
@@ -49,15 +86,354 @@ duration_seconds = 3.0
[[recipe.outputs]]
item = "iron_ingot"
amount = 2
probability = 0.6
probability = 0.45
[[recipe.outputs]]
item = "circuit_board"
item = "copper_ingot"
amount = 1
probability = 0.3
probability = 0.25
[[recipe.outputs]]
item = "titanium_ingot"
amount = 1
probability = 0.15
[[recipe.outputs]]
item = "advanced_alloy"
amount = 1
probability = 0.1
probability = 0.15
# -----------------------------------------------------------------------------
# Basic components (tier 2, early game)
# -----------------------------------------------------------------------------
[[recipe]]
id = "copper_wire"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "copper_ingot", amount = 1}]
outputs = [{item = "copper_wire", amount = 2}]
duration_seconds = 1.5
[[recipe]]
id = "steel_plate"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "iron_ingot", amount = 2}]
outputs = [{item = "steel_plate", amount = 1}]
duration_seconds = 2.0
[[recipe]]
id = "circuit_board"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "iron_ingot", amount = 1}, {item = "copper_wire", amount = 2}]
outputs = [{item = "circuit_board", amount = 1}]
duration_seconds = 2.0
[[recipe]]
id = "building_blocks"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "iron_ingot", amount = 4}]
outputs = [{item = "building_block", amount = 10}]
duration_seconds = 4.0
# -----------------------------------------------------------------------------
# Advanced components (tier 3, midgame)
# -----------------------------------------------------------------------------
[[recipe]]
id = "mechanical_parts"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "steel_plate", amount = 1}, {item = "iron_ingot", amount = 1}]
outputs = [{item = "mechanical_parts", amount = 2}]
duration_seconds = 2.5
[[recipe]]
id = "targeting_unit"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "circuit_board", amount = 2}, {item = "copper_wire", amount = 1}]
outputs = [{item = "targeting_unit", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "drive_unit"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "steel_plate", amount = 1},
{item = "mechanical_parts", amount = 1},
{item = "circuit_board", amount = 1},
]
outputs = [{item = "drive_unit", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "titanium_frame"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "titanium_ingot", amount = 2}, {item = "steel_plate", amount = 1}]
outputs = [{item = "titanium_frame", amount = 1}]
duration_seconds = 4.0
# -----------------------------------------------------------------------------
# Capital components (tier 4, lategame — gated on advanced_alloy)
# -----------------------------------------------------------------------------
[[recipe]]
id = "reinforced_plating"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "steel_plate", amount = 2}, {item = "advanced_alloy", amount = 1}]
outputs = [{item = "reinforced_plating", amount = 1}]
duration_seconds = 5.0
[[recipe]]
id = "capital_core"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "targeting_unit", amount = 1},
{item = "drive_unit", amount = 1},
{item = "advanced_alloy", amount = 2},
]
outputs = [{item = "capital_core", amount = 1}]
duration_seconds = 8.0
# -----------------------------------------------------------------------------
# Module items — early game
# -----------------------------------------------------------------------------
[[recipe]]
id = "laser_cannon_s_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "iron_ingot", amount = 2}, {item = "circuit_board", amount = 1}]
outputs = [{item = "laser_cannon_s_module", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "salvager_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "steel_plate", amount = 1}, {item = "circuit_board", amount = 1}]
outputs = [{item = "salvager_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "repair_tool_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "circuit_board", amount = 2}, {item = "copper_wire", amount = 1}]
outputs = [{item = "repair_tool_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "armor_plates_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "steel_plate", amount = 2}]
outputs = [{item = "armor_plates_module", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "sensor_booster_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "circuit_board", amount = 1}, {item = "copper_wire", amount = 2}]
outputs = [{item = "sensor_booster_module", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "maneuvering_thrusters_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "mechanical_parts", amount = 1}, {item = "copper_wire", amount = 1}]
outputs = [{item = "maneuvering_thrusters_module", amount = 1}]
duration_seconds = 3.0
# -----------------------------------------------------------------------------
# Module items — midgame
# -----------------------------------------------------------------------------
[[recipe]]
id = "afterburner_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "drive_unit", amount = 1}, {item = "steel_plate", amount = 1}]
outputs = [{item = "afterburner_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "weapon_upgrade_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "targeting_unit", amount = 1}, {item = "steel_plate", amount = 1}]
outputs = [{item = "weapon_upgrade_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "weapon_primer_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "targeting_unit", amount = 1}, {item = "copper_wire", amount = 2}]
outputs = [{item = "weapon_primer_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "weapon_stabilizer_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "targeting_unit", amount = 1}, {item = "mechanical_parts", amount = 1}]
outputs = [{item = "weapon_stabilizer_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "laser_cannon_m_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "targeting_unit", amount = 1}, {item = "titanium_frame", amount = 1}]
outputs = [{item = "laser_cannon_m_module", amount = 1}]
duration_seconds = 6.0
[[recipe]]
id = "drone_bay_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "titanium_frame", amount = 1},
{item = "mechanical_parts", amount = 1},
{item = "circuit_board", amount = 1},
]
outputs = [{item = "drone_bay_module", amount = 1}]
duration_seconds = 6.0
# -----------------------------------------------------------------------------
# Module items — lategame
# -----------------------------------------------------------------------------
[[recipe]]
id = "laser_cannon_l_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "targeting_unit", amount = 2},
{item = "reinforced_plating", amount = 2},
{item = "titanium_frame", amount = 1},
]
outputs = [{item = "laser_cannon_l_module", amount = 1}]
duration_seconds = 12.0
[[recipe]]
id = "drone_hangar_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "capital_core", amount = 1},
{item = "titanium_frame", amount = 2},
{item = "reinforced_plating", amount = 2},
]
outputs = [{item = "drone_hangar_module", amount = 1}]
duration_seconds = 20.0
# -----------------------------------------------------------------------------
# Ship hulls
# -----------------------------------------------------------------------------
[[recipe]]
id = "drone_hull"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 1}]
outputs = [{item = "drone_hull", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "frigate_hull"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "steel_plate", amount = 3},
{item = "mechanical_parts", amount = 1},
{item = "circuit_board", amount = 1},
]
outputs = [{item = "frigate_hull", amount = 1}]
duration_seconds = 8.0
[[recipe]]
id = "destroyer_hull"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "steel_plate", amount = 5},
{item = "mechanical_parts", amount = 2},
{item = "circuit_board", amount = 1},
]
outputs = [{item = "destroyer_hull", amount = 1}]
duration_seconds = 10.0
[[recipe]]
id = "cruiser_hull"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "titanium_frame", amount = 2},
{item = "steel_plate", amount = 4},
{item = "drive_unit", amount = 1},
]
outputs = [{item = "cruiser_hull", amount = 1}]
duration_seconds = 15.0
[[recipe]]
id = "battlecruiser_hull"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "titanium_frame", amount = 3},
{item = "steel_plate", amount = 6},
{item = "drive_unit", amount = 1},
{item = "targeting_unit", amount = 1},
]
outputs = [{item = "battlecruiser_hull", amount = 1}]
duration_seconds = 20.0
[[recipe]]
id = "battleship_hull"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "titanium_frame", amount = 4},
{item = "reinforced_plating", amount = 2},
{item = "drive_unit", amount = 2},
]
outputs = [{item = "battleship_hull", amount = 1}]
duration_seconds = 30.0
[[recipe]]
id = "dreadnought_hull"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "capital_core", amount = 1},
{item = "titanium_frame", amount = 6},
{item = "reinforced_plating", amount = 4},
{item = "drive_unit", amount = 2},
]
outputs = [{item = "dreadnought_hull", amount = 1}]
duration_seconds = 60.0
[[recipe]]
id = "carrier_hull"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "capital_core", amount = 1},
{item = "titanium_frame", amount = 5},
{item = "reinforced_plating", amount = 3},
{item = "drive_unit", amount = 2},
]
outputs = [{item = "carrier_hull", amount = 1}]
duration_seconds = 60.0

View File

@@ -1,116 +1,295 @@
# ships.toml
#
# First real-content iteration: ship ids and layout grids are the designed
# content; stats, materials, and production times are placeholders until the
# recipe and balancing passes.
#
# Size classes:
# xs drone 1 cell — exactly one 1x1 module
# s frigate, destroyer no 2x2 area anywhere: only 1x1/1x2/1x3/L modules fit
# m cruiser, battlecruiser 2x2 areas (m guns, drone bays) but no 3x3 area
# l battleship four m guns, or exactly one 3x3 l gun at heavy
# opportunity cost
# xl dreadnought, carrier dreadnought fits three l guns but no drone
# hangar; carrier fits one drone hangar (2x6)
# but no l gun (its deck rows are broken up
# by elevator shafts)
[[ship]]
id = "interceptor"
available_from_start = true
id = "drone"
unlock_at_station_level = -1
layout = ["O"]
default_modules = [{type = "laser_cannon_s", x = 0, y = 0, rotation = "east"}]
[ship.schematic]
materials = [{item = "iron_ingot", amount = 3}, {item = "circuit_board", amount = 1}]
materials = [{item = "iron_ore", amount = 1}]
player_production_level = 1
production_time_seconds = 5
[ship.health]
hp_formula = "3"
[ship.movement]
speed_mps_formula = "40"
main_acceleration_mpss_formula = "80"
maneuvering_acceleration_mpss_formula = "40"
angular_acceleration_radpss_formula = "12.56"
max_rotation_speed_radps_formula = "6.28"
[ship.sensor]
sensor_range_m_formula = "150"
[ship.loot]
scrap_drop = 2
# Frigate — 5 cells in a plus shape. Holds a couple of small guns plus at
# most one 1x2 support (every 1x2 placement crosses the center cell), or one
# L-shaped weapon modifier, or an afterburner spanning the full center line.
[[ship]]
id = "frigate"
unlock_at_station_level = -1
layout = [
"XOX",
"OOO",
"XOX",
]
[ship.schematic]
materials = [{item = "frigate_hull", amount = 1}]
player_production_level = 1
production_time_seconds = 10
[ship.threat]
cost_formula = "10"
[ship.health]
hp_formula = "4"
hp_formula = "30"
[ship.movement]
speed_formula = "4"
speed_mps_formula = "30"
main_acceleration_mpss_formula = "50"
maneuvering_acceleration_mpss_formula = "25"
angular_acceleration_radpss_formula = "8"
max_rotation_speed_radps_formula = "4"
[ship.sensor]
sensor_range_formula = "15"
[ship.combat]
damage_formula = "1"
attack_range_formula = "10"
attack_rate_formula = "2.0"
sensor_range_m_formula = "200"
[ship.loot]
scrap_drop = 2
scrap_drop = 5
# Destroyer — 8 cells: a long gun deck with three turret bumps on top.
# Still no 2x2 area, so it packs more small guns than a frigate but can never
# mount medium hardware.
[[ship]]
id = "destroyer"
available_from_start = true
unlock_at_station_level = -1
layout = [
"OXOXO",
"OOOOO",
]
[ship.schematic]
materials = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 2}]
player_production_level = 5
production_time_seconds = 20
[ship.threat]
cost_formula = "0"
[ship.health]
hp_formula = "120 + 15*x"
[ship.movement]
speed_formula = "120"
[ship.sensor]
sensor_range_formula = "300"
[ship.combat]
damage_formula = "12 + 2*x"
attack_range_formula = "250"
attack_rate_formula = "1.0"
[ship.loot]
scrap_drop = 4
[[ship]]
id = "salvage_ship"
available_from_start = true
[ship.schematic]
materials = [{item = "iron_ingot", amount = 4}]
player_production_level = 3
production_time_seconds = 10
[ship.threat]
cost_formula = "0"
[ship.health]
hp_formula = "40 + 4*x"
[ship.movement]
speed_formula = "110"
[ship.sensor]
sensor_range_formula = "250"
[ship.salvage]
collection_range = 50
cargo_capacity = 10
[ship.loot]
scrap_drop = 2
[[ship]]
id = "repair_ship"
available_from_start = false
[ship.schematic]
materials = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}]
player_production_level = 3
materials = [{item = "destroyer_hull", amount = 1}]
player_production_level = 1
production_time_seconds = 15
[ship.threat]
cost_formula = "0"
[ship.health]
hp_formula = "60 + 5*x"
hp_formula = "50"
[ship.movement]
speed_formula = "130"
speed_mps_formula = "25"
main_acceleration_mpss_formula = "40"
maneuvering_acceleration_mpss_formula = "20"
angular_acceleration_radpss_formula = "6"
max_rotation_speed_radps_formula = "3"
[ship.sensor]
sensor_range_formula = "250"
[ship.repair]
repair_rate_formula = "5 + x"
repair_range_formula = "80"
sensor_range_m_formula = "220"
[ship.loot]
scrap_drop = 2
scrap_drop = 8
# Cruiser — 12 cells with notched corners. Fits at most two 2x2 m guns
# (stacked through the middle), leaving the four side cells for small
# supports; no 3x3 area exists for an l gun.
[[ship]]
id = "cruiser"
unlock_at_station_level = -1
layout = [
"XOOX",
"OOOO",
"OOOO",
"XOOX",
]
[ship.schematic]
materials = [{item = "cruiser_hull", amount = 1}]
player_production_level = 1
production_time_seconds = 25
[ship.health]
hp_formula = "120"
[ship.movement]
speed_mps_formula = "20"
main_acceleration_mpss_formula = "30"
maneuvering_acceleration_mpss_formula = "15"
angular_acceleration_radpss_formula = "4"
max_rotation_speed_radps_formula = "2"
[ship.sensor]
sensor_range_m_formula = "250"
[ship.loot]
scrap_drop = 15
# Battlecruiser — 16 cells: a wide bow split into two gun cheeks, tapering
# toward the stern. Fits three 2x2 m guns (two in the cheeks, one through
# the middle) with small support slots left over; the split bow and tapered
# stern leave no 3x3 area for an l gun and no 2x6 area for a drone hangar.
[[ship]]
id = "battlecruiser"
unlock_at_station_level = -1
layout = [
"OOXXOO",
"OOOOOO",
"XOOOOX",
"XXOOXX",
]
[ship.schematic]
materials = [{item = "battlecruiser_hull", amount = 1}]
player_production_level = 1
production_time_seconds = 35
[ship.health]
hp_formula = "180"
[ship.movement]
speed_mps_formula = "18"
main_acceleration_mpss_formula = "25"
maneuvering_acceleration_mpss_formula = "12"
angular_acceleration_radpss_formula = "3"
max_rotation_speed_radps_formula = "1.5"
[ship.sensor]
sensor_range_m_formula = "260"
[ship.loot]
scrap_drop = 20
# Battleship — 24 cells: a broadside hull with notched flanks on every other
# row. Fits four 2x2 m guns (two per gun deck) with the bow, stern, and flank
# cells left for supports. All 3x3 placements crowd the center columns, so at
# most ONE l gun fits — and mounting it blocks every m gun mount, leaving
# only narrow support strips. The notched rows are never adjacent-and-full,
# so no 2x6 drone hangar fits.
[[ship]]
id = "battleship"
unlock_at_station_level = -1
layout = [
"XOOOOX",
"OOOOOO",
"XOOOOX",
"OOOOOO",
"XOOOOX",
]
[ship.schematic]
materials = [{item = "battleship_hull", amount = 1}]
player_production_level = 1
production_time_seconds = 60
[ship.health]
hp_formula = "350"
[ship.movement]
speed_mps_formula = "14"
main_acceleration_mpss_formula = "18"
maneuvering_acceleration_mpss_formula = "8"
angular_acceleration_radpss_formula = "2"
max_rotation_speed_radps_formula = "1"
[ship.sensor]
sensor_range_m_formula = "280"
[ship.loot]
scrap_drop = 35
# Dreadnought — 36 cells: the main battery deck is split into three 3x3 gun
# slots by structural spacer columns, so exactly three l guns fit side by
# side (or m guns / supports in unused slots). The spacers cap every
# horizontal run at 5 cells, so the 2x6 drone hangar can never fit — carriers
# stay the only hangar hull. Bow and stern strips hold supports.
[[ship]]
id = "dreadnought"
unlock_at_station_level = -1
layout = [
"XXXOOOOOXXX",
"OOOXOOOXOOO",
"OOOXOOOXOOO",
"OOOXOOOXOOO",
"XXOOXXXOOXX",
]
[ship.schematic]
materials = [{item = "dreadnought_hull", amount = 1}]
player_production_level = 1
production_time_seconds = 120
[ship.health]
hp_formula = "800"
[ship.movement]
speed_mps_formula = "8"
main_acceleration_mpss_formula = "10"
maneuvering_acceleration_mpss_formula = "5"
angular_acceleration_radpss_formula = "1"
max_rotation_speed_radps_formula = "0.5"
[ship.sensor]
sensor_range_m_formula = "300"
[ship.loot]
scrap_drop = 60
# Carrier — 37 cells: the top flight deck (rows 0-1) is the only place wide
# enough for the 2x6 drone hangar, and exactly one fits. The middle deck row
# is broken up by elevator shafts (the X cells) so no 3x3 l gun can ever fit;
# the lower decks hold supports and 2x2 point-defense m guns.
[[ship]]
id = "carrier"
unlock_at_station_level = -1
layout = [
"XOOOOOOOOX",
"OOOOOOOOOO",
"OOXOOXOOXO",
"XOOOOOOOOX",
"XXXOOOOXXX",
]
[ship.schematic]
materials = [{item = "carrier_hull", amount = 1}]
player_production_level = 1
production_time_seconds = 120
[ship.health]
hp_formula = "700"
[ship.movement]
speed_mps_formula = "9"
main_acceleration_mpss_formula = "10"
maneuvering_acceleration_mpss_formula = "5"
angular_acceleration_radpss_formula = "1"
max_rotation_speed_radps_formula = "0.5"
[ship.sensor]
sensor_range_m_formula = "350"
[ship.loot]
scrap_drop = 60

View File

@@ -14,8 +14,8 @@ surface_mask = [
level = 1
hp_formula = "300"
damage_formula = "5"
range_formula = "20"
fire_rate_formula = "1"
range_m_formula = "200"
fire_rate_hz_formula = "1"
scrap_drop_formula = "10"
[enemy_station]
@@ -25,6 +25,6 @@ surface_mask = [
]
hp_formula = "300 + 150*x"
damage_formula = "2 + 1*x"
range_formula = "20"
fire_rate_formula = "1.0 + 0.2*x"
range_m_formula = "200"
fire_rate_hz_formula = "1.0 + 0.2*x"
scrap_drop_formula = "10 + 5*x"

View File

@@ -106,6 +106,8 @@ glyph = "E"
# drawn around it. One section per ItemType.
# -----------------------------------------------------------------------------
# --- ores ---
[items.iron_ore]
fill = "#8a5a4a"
outline = "#201010"
@@ -114,6 +116,12 @@ outline = "#201010"
fill = "#c47a3a"
outline = "#3a1a0a"
[items.titanium_ore]
fill = "#9aa3ad"
outline = "#2a2e33"
# --- ingots ---
[items.iron_ingot]
fill = "#b0b0b8"
outline = "#202028"
@@ -122,43 +130,194 @@ outline = "#202028"
fill = "#d48a4a"
outline = "#402010"
[items.circuit_board]
fill = "#2ea35a"
outline = "#0a2a14"
[items.titanium_ingot]
fill = "#c8d2dc"
outline = "#3a4048"
[items.advanced_alloy]
fill = "#a06acc"
outline = "#201030"
[items.building_block]
fill = "#c8b070"
outline = "#302810"
# --- salvage loop ---
[items.scrap]
fill = "#7a7268"
outline = "#201a14"
[items.advanced_alloy]
fill = "#a06acc"
outline = "#201030"
# --- basic components ---
[items.copper_wire]
fill = "#e09a50"
outline = "#3a2008"
[items.steel_plate]
fill = "#8a92a0"
outline = "#22262c"
[items.circuit_board]
fill = "#2ea35a"
outline = "#0a2a14"
[items.building_block]
fill = "#c8b070"
outline = "#302810"
# --- advanced components ---
[items.mechanical_parts]
fill = "#6f7a66"
outline = "#1c2018"
[items.targeting_unit]
fill = "#3a9e8c"
outline = "#0c2824"
[items.drive_unit]
fill = "#4a6ad0"
outline = "#101a38"
[items.titanium_frame]
fill = "#b8c4d4"
outline = "#343c48"
# --- capital components ---
[items.reinforced_plating]
fill = "#8a6ad0"
outline = "#1c1038"
[items.capital_core]
fill = "#b040d0"
outline = "#280c30"
# --- module items ---
[items.laser_cannon_s_module]
fill = "#691313"
outline = "#f3ff4f"
[items.laser_cannon_m_module]
fill = "#892020"
outline = "#f3ff4f"
[items.laser_cannon_l_module]
fill = "#a92d2d"
outline = "#f3ff4f"
[items.salvager_module]
fill = "#b2cfdd"
outline = "#236137"
[items.repair_tool_module]
fill = "#2e9ba3"
outline = "#689275"
[items.armor_plates_module]
fill = "#808080"
outline = "#202020"
[items.sensor_booster_module]
fill = "#40a0ff"
outline = "#102840"
[items.maneuvering_thrusters_module]
fill = "#5090e0"
outline = "#142438"
[items.afterburner_module]
fill = "#6080c0"
outline = "#182030"
[items.weapon_upgrade_module]
fill = "#ff4040"
outline = "#401010"
[items.weapon_primer_module]
fill = "#e03838"
outline = "#380e0e"
[items.weapon_stabilizer_module]
fill = "#c03030"
outline = "#300c0c"
[items.drone_bay_module]
fill = "#cc66ff"
outline = "#331040"
[items.drone_hangar_module]
fill = "#9933cc"
outline = "#260c33"
# --- ship hulls (outline matches the ship's fleet color in [ships.*]) ---
[items.drone_hull]
fill = "#1b1b1b"
outline = "#3366ff"
[items.frigate_hull]
fill = "#1b1b1b"
outline = "#44aaff"
[items.destroyer_hull]
fill = "#1b1b1b"
outline = "#33ccaa"
[items.cruiser_hull]
fill = "#1b1b1b"
outline = "#66cc33"
[items.battlecruiser_hull]
fill = "#1b1b1b"
outline = "#cccc33"
[items.battleship_hull]
fill = "#1b1b1b"
outline = "#ff9933"
[items.dreadnought_hull]
fill = "#1b1b1b"
outline = "#ff5533"
[items.carrier_hull]
fill = "#1b1b1b"
outline = "#cc66ff"
# -----------------------------------------------------------------------------
# Ships
#
# Ships are drawn as oriented triangles/arrows. Color is keyed to role, not
# schematic (architecture.md, "Layer Order").
# Ships are drawn as oriented triangles/arrows. Color is keyed to schematic id.
# -----------------------------------------------------------------------------
[ships.player_combat]
[ships.drone]
fill = "#3366ff"
outline = "#ffffff"
[ships.salvage]
fill = "#33cc66"
[ships.frigate]
fill = "#44aaff"
outline = "#ffffff"
[ships.repair]
fill = "#66ccff"
[ships.destroyer]
fill = "#33ccaa"
outline = "#ffffff"
[ships.enemy]
fill = "#cc3333"
[ships.cruiser]
fill = "#66cc33"
outline = "#ffffff"
[ships.battlecruiser]
fill = "#cccc33"
outline = "#ffffff"
[ships.battleship]
fill = "#ff9933"
outline = "#ffffff"
[ships.dreadnought]
fill = "#ff5533"
outline = "#ffffff"
[ships.carrier]
fill = "#cc66ff"
outline = "#ffffff"
# -----------------------------------------------------------------------------

View File

@@ -3,27 +3,32 @@ height_tiles = 30
refund_percentage = 75
starting_building_blocks = 1000
scrap_despawn_seconds = 30
belt_speed_tiles_per_second = 2
tunnel_max_distance = 10
tile_size_m = 10
belt_speed_mps = 20
tunnel_max_distance_tiles = 10
departure_interval_seconds = 20
[regions]
asteroid_width = 40
player_buffer_width = 20
contest_zone_width = 60
enemy_buffer_width = 20
asteroid_width_tiles = 40
player_buffer_width_tiles = 20
contest_zone_width_tiles = 60
enemy_buffer_width_tiles = 20
[expansion]
columns_per_expansion = 10
columns_per_expansion_tiles = 10
cost_building_blocks = 200
[push]
push_expand_columns = 10
scaling_factor = 1.2
push_expand_columns_tiles = 10
boss_advance_seconds = 60
[waves]
threat_rate_formula = "0.01*x"
threat_rate_formula = "x"
ship_level_formula = "1"
gap_min_seconds = 15
gap_max_seconds = 45
spawn_duration_seconds = 10
boss_countdown_seconds = 300
boss_threat_duration_seconds = 60
boss_quiet_before_seconds = 20
boss_quiet_after_seconds = 20

View File

@@ -0,0 +1,103 @@
[[arena]]
name = "Fighters vs Sniper"
height_tiles = 20
player_buffer_width_tiles = 10
contest_zone_width_tiles = 60
enemy_buffer_width_tiles = 10
[[arena.team]]
name = "Alpha"
[[arena.team.ship]]
schematic = "drone"
level = 1
count = 5
modules = [
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
]
[[arena.team]]
name = "Beta"
[[arena.team.ship]]
schematic = "drone"
level = 1
count = 2
modules = [
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
{type = "weapon_stabilizer", x = 1, y = 1, rotation = "east"},
{type = "weapon_stabilizer", x = 1, y = 1, rotation = "east"},
{type = "weapon_upgrade", x = 1, y = 1, rotation = "east"},
{type = "sensor_booster", x = 1, y = 1, rotation = "east"},
{type = "sensor_booster", x = 1, y = 1, rotation = "east"},
]
[[arena]]
name = "Fighters vs Supported"
height_tiles = 20
player_buffer_width_tiles = 10
contest_zone_width_tiles = 60
enemy_buffer_width_tiles = 10
[[arena.team]]
name = "Fighters"
[[arena.team.ship]]
schematic = "drone"
level = 1
count = 5
modules = [
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
]
[[arena.team]]
name = "Supported"
[[arena.team.ship]]
schematic = "drone"
level = 1
count = 3
modules = [
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
]
[[arena.team.ship]]
schematic = "drone"
level = 1
count = 2
modules = [
{type = "repair_tool", x = 1, y = 1, rotation = "east"},
]
[[arena]]
name = "Stations and Ships"
height_tiles = 60
player_buffer_width_tiles = 15
contest_zone_width_tiles = 40
enemy_buffer_width_tiles = 15
[[arena.team]]
name = "Fortified"
[[arena.team.ship]]
schematic = "drone"
level = 1
count = 3
modules = [
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
]
[[arena.team.station]]
type = "player_station"
x = 8
y = 15
level = 1
[[arena.team.station]]
type = "player_station"
x = 8
y = 45
level = 1
[[arena.team]]
name = "Swarm"
[[arena.team.ship]]
schematic = "drone"
level = 1
count = 8
modules = [
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
]

View File

@@ -0,0 +1,137 @@
[[module]]
id = "armor_plate"
unlock_at_station_level = -1
surface_mask = ["OO"]
materials = [{item = "iron_ingot", amount = 2}]
player_production_level = 1
production_time_seconds = 3
fill_color = "#808080"
glyph = "A"
[module.health]
multiplied_hp_formula = "1.5"
[[module]]
id = "sensor_booster"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "circuit_board", amount = 1}]
player_production_level = 1
production_time_seconds = 2
fill_color = "#40A0FF"
glyph = "S"
[module.sensor]
added_sensor_range_m_formula = "100"
[[module]]
id = "weapon_upgrade"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "iron_ingot", amount = 1}, {item = "circuit_board", amount = 1}]
player_production_level = 1
production_time_seconds = 4
fill_color = "#FF4040"
glyph = "W"
[module.weapon]
multiplied_damage_formula = "1.2"
[[module]]
id = "laser_cannon"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "iron_ingot", amount = 1}]
player_production_level = 1
production_time_seconds = 5
fill_color = "#FF8040"
glyph = "L"
[module.weapon]
damage_formula = "2"
attack_range_m_formula = "50"
attack_rate_hz_formula = "2.0"
[[module]]
id = "salvager"
unlock_at_station_level = -1
surface_mask = ["OO"]
materials = [{item = "iron_ingot", amount = 2}]
player_production_level = 1
production_time_seconds = 5
fill_color = "#AACC44"
glyph = "Sv"
[module.salvage]
collection_range_m_formula = "500"
cargo_capacity_formula = "10"
collection_rate_hz_formula = "0.5"
[[module]]
id = "repair_tool"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "circuit_board", amount = 2}]
player_production_level = 1
production_time_seconds = 5
fill_color = "#66CCFF"
glyph = "Rp"
[module.repair]
repair_rate_hz_formula = "5 + x"
repair_range_m_formula = "800"
[[module]]
id = "weapon_primer"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "iron_ingot", amount = 1}]
player_production_level = 1
production_time_seconds = 4
fill_color = "#FF4040"
glyph = "Wp"
[module.weapon]
multiplied_attack_rate_hz_formula = "1.2"
[[module]]
id = "weapon_stabilizer"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "iron_ingot", amount = 1}]
player_production_level = 1
production_time_seconds = 4
fill_color = "#FF4040"
glyph = "Ws"
[module.weapon]
multiplied_attack_range_m_formula = "1.5"
multiplied_attack_rate_hz_formula = "0.8"
[[module]]
id = "afterburner"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "iron_ingot", amount = 1}]
player_production_level = 1
production_time_seconds = 2
fill_color = "#40A0FF"
glyph = "Ab"
[module.movement]
multiplied_speed_mps_formula = "1.6"
added_main_acceleration_mpss_formula = "60"
[[module]]
id = "maneuvering_thrusters"
unlock_at_station_level = -1
surface_mask = ["O"]
materials = [{item = "iron_ingot", amount = 1}]
player_production_level = 1
production_time_seconds = 2
fill_color = "#40A0FF"
glyph = "Mt"
[module.movement]
multiplied_speed_mps_formula = "1.2"
added_maneuvering_acceleration_mpss_formula = "10"

View File

@@ -40,6 +40,38 @@ inputs = [{item = "iron_ingot", amount = 4}]
outputs = [{item = "building_block", amount = 10}]
duration_seconds = 4.0
[[recipe]]
id = "premium_circuit"
building = "assembler"
unlock_at_station_level = -1
inputs = [{item = "circuit_board", amount = 1}]
outputs = [{item = "premium_circuit", amount = 1}]
duration_seconds = 8.0
[[recipe]]
id = "quick_circuit"
building = "assembler"
unlock_at_station_level = 0
inputs = [{item = "copper_ingot", amount = 3}]
outputs = [{item = "circuit_board", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "advanced_circuit"
building = "assembler"
unlock_at_station_level = 1
inputs = [{item = "iron_ingot", amount = 5}]
outputs = [{item = "circuit_board", amount = 1}]
duration_seconds = 6.0
[[recipe]]
id = "exotic_alloy"
building = "assembler"
unlock_at_station_level = 0
inputs = [{item = "exotic_ore", amount = 2}]
outputs = [{item = "exotic_alloy", amount = 1}]
duration_seconds = 10.0
[[recipe]]
id = "reprocessing_cycle"
building = "reprocessing_plant"

View File

@@ -1,28 +1,26 @@
[[ship]]
id = "interceptor"
available_from_start = true
unlock_at_station_level = -1
layout = ["XOX", "OOO", "XOX"]
default_modules = [{type = "laser_cannon", x = 1, y = 1, rotation = "east"}]
[ship.schematic]
materials = [{item = "iron_ingot", amount = 3}, {item = "circuit_board", amount = 1}]
player_production_level = 3
production_time_seconds = 10
[ship.threat]
cost_formula = "5 + 1*x"
[ship.health]
hp_formula = "40 + 5*x"
[ship.movement]
speed_formula = "200 + 5*x"
speed_mps_formula = "2000 + 50*x"
main_acceleration_mpss_formula = "1000000"
maneuvering_acceleration_mpss_formula = "1000000"
angular_acceleration_radpss_formula = "100000"
max_rotation_speed_radps_formula = "100000"
[ship.sensor]
sensor_range_formula = "200"
[ship.combat]
damage_formula = "10 + 2*x"
attack_range_formula = "150"
attack_rate_formula = "2.0"
sensor_range_m_formula = "2000"
[ship.loot]
scrap_drop = 2
@@ -30,29 +28,27 @@ scrap_drop = 2
[[ship]]
id = "destroyer"
available_from_start = true
unlock_at_station_level = -1
layout = ["XOOX", "OOOO", "XOOX"]
default_modules = [{type = "laser_cannon", x = 1, y = 1, rotation = "east"}]
[ship.schematic]
materials = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 2}]
player_production_level = 5
production_time_seconds = 20
[ship.threat]
cost_formula = "10 + 2*x"
[ship.health]
hp_formula = "120 + 15*x"
[ship.movement]
speed_formula = "120"
speed_mps_formula = "1200"
main_acceleration_mpss_formula = "1000000"
maneuvering_acceleration_mpss_formula = "1000000"
angular_acceleration_radpss_formula = "100000"
max_rotation_speed_radps_formula = "100000"
[ship.sensor]
sensor_range_formula = "300"
[ship.combat]
damage_formula = "12 + 2*x"
attack_range_formula = "250"
attack_rate_formula = "1.0"
sensor_range_m_formula = "3000"
[ship.loot]
scrap_drop = 4
@@ -60,28 +56,26 @@ scrap_drop = 4
[[ship]]
id = "salvage_ship"
available_from_start = true
unlock_at_station_level = -1
layout = ["OOO", "OOO"]
[ship.schematic]
materials = [{item = "iron_ingot", amount = 4}]
player_production_level = 3
production_time_seconds = 10
[ship.threat]
cost_formula = "0"
[ship.health]
hp_formula = "40 + 4*x"
[ship.movement]
speed_formula = "110"
speed_mps_formula = "1100"
main_acceleration_mpss_formula = "1000000"
maneuvering_acceleration_mpss_formula = "1000000"
angular_acceleration_radpss_formula = "100000"
max_rotation_speed_radps_formula = "100000"
[ship.sensor]
sensor_range_formula = "250"
[ship.salvage]
collection_range = 50
cargo_capacity = 10
sensor_range_m_formula = "2500"
[ship.loot]
scrap_drop = 2
@@ -89,28 +83,26 @@ scrap_drop = 2
[[ship]]
id = "repair_ship"
available_from_start = false
unlock_at_station_level = 0
layout = ["XOX", "OOO", "XOX"]
[ship.schematic]
materials = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}]
player_production_level = 3
production_time_seconds = 15
[ship.threat]
cost_formula = "0"
[ship.health]
hp_formula = "60 + 5*x"
[ship.movement]
speed_formula = "130"
speed_mps_formula = "1300"
main_acceleration_mpss_formula = "1000000"
maneuvering_acceleration_mpss_formula = "1000000"
angular_acceleration_radpss_formula = "100000"
max_rotation_speed_radps_formula = "100000"
[ship.sensor]
sensor_range_formula = "250"
[ship.repair]
repair_rate_formula = "5 + x"
repair_range_formula = "80"
sensor_range_m_formula = "2500"
[ship.loot]
scrap_drop = 2

View File

@@ -14,8 +14,8 @@ surface_mask = [
level = 5
hp_formula = "300 + 40*x"
damage_formula = "5 + 4*x"
range_formula = "300 + 20*x"
fire_rate_formula = "0.5 + 0.2*x"
range_m_formula = "3000 + 200*x"
fire_rate_hz_formula = "0.5 + 0.2*x"
scrap_drop_formula = "x"
[enemy_station]
@@ -25,6 +25,6 @@ surface_mask = [
]
hp_formula = "300 + 150*x"
damage_formula = "20 + 10*x"
range_formula = "350 + 20*x"
fire_rate_formula = "1.0 + 0.2*x"
range_m_formula = "3500 + 200*x"
fire_rate_hz_formula = "1.0 + 0.2*x"
scrap_drop_formula = "10 + 5*x"

View File

@@ -3,27 +3,32 @@ height_tiles = 60
refund_percentage = 75
starting_building_blocks = 100
scrap_despawn_seconds = 30
belt_speed_tiles_per_second = 2
tunnel_max_distance = 10
tile_size_m = 10
belt_speed_mps = 20
tunnel_max_distance_tiles = 10
departure_interval_seconds = 20
[regions]
asteroid_width = 40
player_buffer_width = 10
contest_zone_width = 30
enemy_buffer_width = 15
asteroid_width_tiles = 40
player_buffer_width_tiles = 10
contest_zone_width_tiles = 30
enemy_buffer_width_tiles = 15
[expansion]
columns_per_expansion = 10
columns_per_expansion_tiles = 10
cost_building_blocks = 200
[push]
push_expand_columns = 20
scaling_factor = 1.2
push_expand_columns_tiles = 20
boss_advance_seconds = 60
[waves]
threat_rate_formula = "1*x - 30"
ship_level_formula = "1 + x / 120"
threat_rate_formula = "x"
ship_level_formula = "1 + x / 10"
gap_min_seconds = 15
gap_max_seconds = 45
spawn_duration_seconds = 10
boss_countdown_seconds = 300
boss_threat_duration_seconds = 60
boss_quiet_before_seconds = 60
boss_quiet_after_seconds = 60

View File

@@ -59,23 +59,43 @@ Simulation types shared across subsystems:
- `Item``struct Item { ItemType type; }`. Items on belts have no persistent identity across ticks.
- `Port``struct Port { QPoint tile; Rotation direction; }`. Identifies a belt-adjacent cell and the direction of flow across that cell.
- `MovementIntent``struct MovementIntent { int priority; QVector2D target; }`. Priority follows the order declared under Movement Arbitration. Cleared at the start of each tick; the highest-priority write wins; `tickMovement` reads the winner.
- `FireEvent``struct FireEvent { EntityId shooter; EntityId target; Tick emittedAt; }`. Transient record emitted each time a weapon fires (REQ-SHP-FIRING, REQ-SHP-FIRING-BEAM). Buffered in a sim-owned queue and drained by the renderer; see Sim → UI Events.
- `SchematicDropEvent``struct SchematicDropEvent { ShipSchematicId schematic; int newLevel; bool wasNewUnlock; }`. Emitted when a destroyed enemy-defence-station set awards a schematic (REQ-DEF-SCHEMATIC-DROP). The UI renders a toast (REQ-UI-SCHEMATIC-TOAST); `wasNewUnlock` chooses between the "unlocked" and "level → N" wording.
- `WeaponFiredEvent``struct WeaponFiredEvent : public Event { entt::entity shooter; entt::entity target; Tick emittedAt; }`. Transient record emitted each time a weapon fires (REQ-SHP-FIRING, REQ-SHP-FIRING-BEAM). Buffered in a sim-owned vector during the tick, then drained and re-emitted via EventManager by the UI frame handler; see Sim → UI Events.
- `SchematicChoiceOption``struct SchematicChoiceOption { string schematicId; SchematicType type; string displayName; bool isNewUnlock; int targetLevel; }`. Describes one option in the schematic choice dialog (REQ-DEF-SCHEMATIC-DROP). Up to three are generated when an enemy station set is destroyed. `SchematicType` is `Ship`, `Module`, or `Recipe`.
- `SchematicChoicesAvailableEvent` — EventManager event carrying a `vector<SchematicChoiceOption>`. Sent by the UI each frame when pending choices are detected; handled by `MainWindow` which opens the schematic choice dialog.
## Sim → UI Events
## Event System
The sim owns a small set of per-frame event queues that the UI drains on each render. These carry one-shot signals that are not derivable from persistent state — currently weapon fires (REQ-SHP-FIRING-BEAM) and schematic drops (REQ-UI-SCHEMATIC-TOAST). Additional event types can be added here later (e.g., building-complete, unit-death flashes) without changing the pattern.
All inter-component communication — both sim→UI and UI→UI — uses a unified `EventManager`/`EventHandler` system. No custom Qt signals/slots are used for inter-widget communication.
Implementation: a plain `std::vector<FireEvent>` owned by `Simulation`, one vector per event type. Combat resolution (tick-order step 8) appends to it. The UI calls `simulation.drainFireEvents()` once per rendered frame, which returns the accumulated vector by move and clears the internal one. Beams are tracked by the renderer for 0.3 s of wall time (9 ticks at 30 Hz) using the events' `emittedAt` tick, then discarded. If either the shooter or target entity is gone when the renderer looks them up, the beam is dropped early.
### EventManager
We deliberately do **not** use `QObject` signals/slots or `QEvent`:
`EventManager` is a singleton (`EventManager::getInstance()`) that routes events to registered handlers.
- **Determinism.** A plain ordered vector preserves tick-order exactly; the queue is part of per-tick state, inspectable in tests.
- **Sim/UI seam.** The sim exposes pull-style access only; the UI never subscribes into the sim, keeping the simulation/presentation split clean.
- **Headless testability.** Catch2 tests read the queue directly after `tick()`; no event loop, no `QApplication`.
- **Zero overhead.** Sim types remain plain structs — no `QObject`, no moc, no signal dispatch machinery.
- `sendEventImmediately(shared_ptr<Event>)` — synchronous dispatch to all handlers of the event's type.
- `addEvent(shared_ptr<Event>)` — queues the event for later batch processing.
- `processEvents()` — drains the queue, dispatching each event to its handlers.
If the number of event types grows past a handful, we can wrap them in a small `EventQueue<T>` template, still owned by the sim. Signals/slots would only be warranted if we needed multiple independent subscribers or cross-thread dispatch, and we need neither.
The EventManager is thread-safe (mutex-guarded).
### EventHandler
`EventHandler<T>` is a CRTP-style template that a class inherits to receive events of type `T`. It provides `registerForEvent()` / `unregisterForEvent()` and requires an override of `handleEvent(shared_ptr<const T>)`.
`CombinedEventHandler<Ts...>` is a variadic template for classes that handle multiple event types. It provides `registerForEvents()` / `unregisterForEvents()` and requires one `handleEvent` override per type.
### Sim → UI Events
The simulation layer stays free of EventManager — it uses a plain `std::vector<WeaponFiredEvent>` internally (owned by `CombatSystem`). This preserves determinism, tick-order fidelity, and headless testability (Catch2 tests read the queue directly via `drainWeaponFiredEvents()` after `tick()`).
The UI frame handler (`GameWorldView::onFrame` / `ArenaView::onFrame`) bridges the gap: each frame it calls `simulation.drainWeaponFiredEvents()`, then re-emits each `WeaponFiredEvent` via `EventManager::sendEventImmediately()`. Subscribers (the same view's `handleEvent(WeaponFiredEvent)`) create `ActiveBeam` records tracked for 0.3 s of wall time, then discarded. If either the shooter or target entity is gone when the renderer looks them up, the beam is dropped early.
Schematic drops: when an enemy station set is destroyed, the simulation generates up to 3 `SchematicChoiceOption` entries and stores them as pending state. The UI polls `hasSchematicChoicesPending()` each frame and, when true, sends a `SchematicChoicesAvailableEvent` via EventManager. `MainWindow` handles this event by pausing the game and opening a modal `SchematicChoiceDialog`. The player's selection is fed back via `applySchematicChoice(index)`.
### UI Events
All UI interactions — building selection, builder/blueprint mode transitions, speed changes, demolish mode, escape menu, layout dialog requests — are communicated via EventManager events rather than Qt signals/slots. Each event is a small struct inheriting `Event` (e.g., `SelectionChangedEvent`, `BuildingTypeSelectedEvent`, `SpeedChangeRequestedEvent`). Widgets register as `CombinedEventHandler` for the events they care about and emit events via `EventManager::sendEventImmediately()`.
Bidirectional interactions use separate request/notification event types to avoid infinite recursion (e.g., `ExitBuilderModeRequestedEvent` from `BuildButtonGrid``GameWorldView`, vs. `BuilderModeExitedEvent` from `GameWorldView``BuildButtonGrid`).
## Tick Order
@@ -88,8 +108,8 @@ Within a single simulation tick, subsystems run in this fixed order. The order i
5. **Building → belt push** — buildings push items from output buffer onto the belt tile at their output port (REQ-MAT-OUTPUT-PORT).
6. **Belt tick** — advance items along belt tiles; apply splitter routing (REQ-BLD-SPLITTER).
7. **Ship behavior systems** — clear `MovementIntent` on each ship, then run `tickThreatResponse`, `tickScrapCollector`, `tickRepairBehavior`, `tickHomeReturn` in any order (arbitration is via intent priority).
8. **Combat resolution** — ships and defence stations acquire targets, fire, apply damage; queue deaths. Each fire appends a `FireEvent` to the sim's fire-event queue (REQ-SHP-FIRING-BEAM).
9. **Deaths & loot** — process queued deaths: drop scrap (REQ-RES-SCRAP-DROP); if a full enemy-defence-station set was destroyed this tick, award one schematic (REQ-DEF-SCHEMATIC-DROP) and append a `SchematicDropEvent`; remove entities.
8. **Combat resolution** — ships and defence stations acquire targets, fire, apply damage; queue deaths. Each fire appends a `WeaponFiredEvent` to the sim's weapon-fired-event queue (REQ-SHP-FIRING-BEAM).
9. **Deaths & loot** — process queued deaths: drop scrap (REQ-RES-SCRAP-DROP); if a full enemy-defence-station set was destroyed this tick, generate up to 3 schematic choice options (REQ-DEF-SCHEMATIC-DROP) stored as pending state for the UI to present; remove entities.
10. **`tickMovement`** — advance ship positions based on final `MovementIntent`.
11. **Scrap despawn** — decrement scrap timers; remove expired scrap (REQ-RES-SCRAP-DROP).
@@ -280,7 +300,7 @@ The game world is rendered by a single `GameWorldView` widget that inherits `QOp
### Threading
Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly without locks. If profiling later justifies moving the sim to a worker thread, the pull-style `drainFireEvents()` / `drainSchematicDropEvents()` / `forEachVisualItem()` APIs already support a clean snapshot-and-render split; a single mutex at the sim boundary would suffice.
Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly without locks. If profiling later justifies moving the sim to a worker thread, the pull-style `drainWeaponFiredEvents()` / `getPendingSchematicChoices()` / `applySchematicChoice()` / `forEachVisualItem()` APIs already support a clean snapshot-and-render split; a single mutex at the sim boundary would suffice. The `ArenaSimulation` used by the balancing tool runs headlessly on a worker thread; fire events accumulate in its internal vector and are only drained when `ArenaView` drives `tickOnce()` on the main thread during interactive inspection.
### Layer Order (back to front)
@@ -289,9 +309,9 @@ Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly
3. **Belt items** — 10×10 colored squares emitted by `BeltSystem::forEachVisualItem`.
4. **Scrap** — glyphs at world positions.
5. **Ships** — colored arrows oriented by velocity; color keyed to role (player combat / salvage / repair / enemy).
6. **Laser beams** — lines derived from live `FireEvent`s kept by the renderer for 0.3 s (REQ-SHP-FIRING-BEAM).
6. **Laser beams** — lines derived from live `WeaponFiredEvent`s kept by the renderer for 0.3 s (REQ-SHP-FIRING-BEAM).
7. **Build overlays** — ghost in builder mode (REQ-BLD-GHOST), demolish-mode tint, tile highlight under cursor, box-drag selection rectangle.
8. **Screen-space UI** schematic toasts (REQ-UI-SCHEMATIC-TOAST) and any other screen-anchored elements, drawn after resetting the world-space transform.
8. **Screen-space UI** — screen-anchored elements, drawn after resetting the world-space transform.
### Coordinates and Scrolling

194
docs/content_design.md Normal file
View File

@@ -0,0 +1,194 @@
# Content Design — Ships & Modules
First real-content iterations (June 2026). Pass 1 defined ship hull grids and
module surface masks; pass 2 defined the production tree (recipes). Stats and
threat costs in the config files are still placeholders for the balancing
pass.
## Design principle: footprint gating
Which module fits on which hull is controlled purely by geometry — no
explicit allow-lists. Each hull grid is shaped so that it physically cannot
contain the footprint of modules from a larger size class. This keeps the
rules transparent to the player ("it doesn't fit because there is no room")
and makes them trivially moddable through the config files alone.
### Module footprint ladder
| Footprint | Modules | Smallest hull that fits it |
|-----------|---------|----------------------------|
| 1x1 | laser_cannon_s, salvager, repair_tool | drone |
| 1x2 | maneuvering_thrusters, sensor_booster, armor_plates | frigate |
| 1x3 | afterburner | frigate (eats most of it) |
| L-shape (3 cells) | weapon_stabilizer, weapon_primer, weapon_upgrade | frigate |
| 2x2 | laser_cannon_m, drone_bay | cruiser |
| 3x3 | laser_cannon_l | battleship |
| 2x6 | drone_hangar | carrier (only) |
### Hull grids
`O` = buildable cell, `X` = hull structure (not buildable).
**drone (xs, 1 cell)** — exactly one 1x1 module: a small gun, a salvager, or
a repair tool. This is what makes drone roles swappable.
O
**frigate (s, 5 cells)** — plus shape. Every 1x2 placement crosses the center
cell, so at most ONE 1x2 support fits; alternatively one L-shaped weapon
modifier or one afterburner through the center line. Gun-boat with one or two
support modules, as intended.
XOX
OOO
XOX
**destroyer (s, 8 cells)** — gun deck with three turret bumps. More cells
than the frigate (more small guns), but still no 2x2 area anywhere, so medium
hardware can never be mounted.
OXOXO
OOOOO
**cruiser (m, 12 cells)** — notched corners. Fits at most two 2x2 m guns
(stacked through the middle), leaving the side cells for supports. No 3x3
area.
XOOX
OOOO
OOOO
XOOX
**battlecruiser (m, 16 cells)** — split bow with two gun cheeks, tapered
stern. Fits three 2x2 m guns — one more than the cruiser — with small support
slots left over. The bow split and stern taper prevent any 3x3 area (no l
gun) and any 2x6 area (no drone hangar).
OOXXOO
OOOOOO
XOOOOX
XXOOXX
**battleship (l, 24 cells)** — broadside hull with notched flanks on every
other row. Fits four 2x2 m guns (two per gun deck) — one more than the
battlecruiser — with bow, stern, and flank cells for supports. All 3x3
placements crowd the center columns, so at most ONE l gun fits: mounted
center it blocks every m gun mount (pure support strips remain), mounted
offset it still allows two m guns. The notched rows are never adjacent-and-
full, so no 2x6 drone hangar fits.
XOOOOX
OOOOOO
XOOOOX
OOOOOO
XOOOOX
**dreadnought (xl, 36 cells)** — the main battery deck is split into three
3x3 gun slots by structural spacer columns, so exactly three l guns fit side
by side (or m guns / supports in unused slots), plus bow/stern strips for
supports. The spacers cap every horizontal run at 5 cells, so the 2x6 drone
hangar can never fit — the carrier stays the only hangar hull.
XXXOOOOOXXX
OOOXOOOXOOO
OOOXOOOXOOO
OOOXOOOXOOO
XXOOXXXOOXX
**carrier (xl, 37 cells)** — the top flight deck (rows 01) is the only
region wide enough for the 2x6 drone hangar, and exactly one fits. The middle
deck row is broken up by elevator shafts (X cells placed so every 3-column
window hits one), which is what prevents any 3x3 l gun from ever fitting.
Lower decks hold supports and 2x2 point-defense m guns.
XOOOOOOOOX
OOOOOOOOOO
OOXOOXOOXO
XOOOOOOOOX
XXXOOOOXXX
### Verified gating matrix
Checked programmatically against the configs (all four mask rotations,
all placements) with `tools/verify_layouts.py` — re-run it after editing
layout grids or surface masks:
python dota_factory/tools/verify_layouts.py
| Footprint | drone | frigate | destroyer | cruiser | battlecruiser | battleship | dreadnought | carrier |
|-----------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 1x1 | x | x | x | x | x | x | x | x |
| 1x2 | | x | x | x | x | x | x | x |
| 1x3 | | x | x | x | x | x | x | x |
| L-shape | | x | x | x | x | x | x | x |
| 2x2 | | | | x | x | x | x | x |
| 3x3 | | | | | | x | x | |
| 2x6 | | | | | | | | x |
Maximum simultaneous (disjoint) placements: m guns — cruiser 2,
battlecruiser 3, battleship 4; l guns — battleship 1, dreadnought 3;
drone hangar — carrier 1.
## Production tree
Design principle: each game phase adds exactly one new base input chain, so
factory complexity ramps alongside ship size.
| Phase | New input | How acquired | Unlocks |
|-------|-----------|--------------|---------|
| early | iron_ore, copper_ore | mined | drone, frigate, destroyer; small guns and basic supports |
| mid | titanium_ore | mined (3x slower than iron) | cruiser, battlecruiser; m guns, drone bay, weapon modifiers |
| late | advanced_alloy | ONLY from reprocessing salvaged scrap | battleship, dreadnought, carrier; l guns, drone hangar |
The advanced_alloy gate is the core loop hook: capital ship production
requires fighting (salvaging scrap from kills and reprocessing it), not just
mining. The reprocessing plant turns 5 scrap into iron/copper/titanium ingots
or advanced_alloy probabilistically.
Intermediate components, by tier:
- **Tier 2 (early):** copper_wire (copper), steel_plate (iron), circuit_board
(iron + wire), building_block (iron).
- **Tier 3 (mid):** mechanical_parts (steel + iron), targeting_unit (circuits
+ wire), drive_unit (steel + mechanical_parts + circuit), titanium_frame
(titanium + steel).
- **Tier 4 (late):** reinforced_plating (steel + advanced_alloy),
capital_core (targeting_unit + drive_unit + 2 advanced_alloy).
Hulls and modules consume intermediates of their tier: early items are built
from tier-2 parts, midgame items require tier-3 parts (deeper chains, more
assemblers), capital items require tier-4 parts (and therefore combat). Hull
items are named `<ship>_hull`; module items `<module>_module`. Every item has
an `[items.*]` entry in visuals.toml; hull item outlines match the ship's
fleet color from `[ships.*]`.
Consistency is checked by `tools/verify_recipes.py` — re-run it after editing
recipes, ship/module materials, or visuals:
python dota_factory/tools/verify_recipes.py
It verifies every consumed item has a producer, every item has a visuals
entry, flags orphaned items, and prints which items are reprocessing-only
(currently exactly advanced_alloy).
## Deliberate placeholders / open questions for later passes
- All new hulls have `threat.cost_formula = "0"` so enemy waves do not spawn
them yet (WaveSystem treats any ship with positive threat cost as wave-
eligible, regardless of unlock level). The balancing pass should set real
threat costs together with `default_modules` loadouts so waves spawn them
armed.
- All new hulls and all assembler recipes are `unlock_at_station_level = -1`
(available from the start) to make testing easy; the balancing pass should
stagger these so mid/lategame recipes drop as schematics from enemy defence
stations.
- Recipe quantities and durations are a first guess, deliberately roughly
tiered (capital hulls ~60 s, drones 4 s); the balancing pass tunes them.
- `drone_bay` and `drone_hangar` are footprint-only placeholders: the drone
launching capability does not exist in the simulation yet, so they define
no capability section.
- Renames in this pass: `laser_cannon_xs``laser_cannon_s` (the old 2x2
`laser_cannon_s` became `laser_cannon_m`), `armor_plate``armor_plates`,
`manuvering_thrusters``maneuvering_thrusters` (typo fix). Test data
under `bin/test/data/config` intentionally still uses the old ids — it is
an independent fixture set.

View File

@@ -4,12 +4,14 @@
Config files use the TOML format. The following config files drive game parameters:
- **world.toml** — world dimensions, region widths, expansion amounts, building refund percentage, wave timing, enemy ship level formula, belt speed, starting building blocks, departure interval.
- **world.toml** — world dimensions, region widths, expansion amounts, building refund percentage, wave timing, boss wave timing, enemy ship level formula, belt speed, starting building blocks, departure interval.
- **buildings.toml** — building block cost and construction time per building type.
- **recipes.toml** — crafting recipes: inputs, outputs, quantities, durations, and reprocessing plant probabilities.
- **ships.toml** — per schematic: a human-readable display name (used in toasts and UI), ship stats (HP, speed, damage, attack range, attack rate, sensor range) as formulas of ship level, required build materials, threat cost formula, player production level, and whether the schematic is available from game start.
- **recipes.toml** — crafting recipes: inputs, outputs, quantities, durations, and reprocessing plant probabilities. Assembler recipe entries may optionally define `unlock_at_station_level` (integer): -1 means the recipe is explicitly unlocked at game start; a value ≥ 0 means the recipe starts locked and a schematic for it can be awarded via defence station destruction (see REQ-LOCK-EXPLICIT, REQ-DEF-SCHEMATIC-DROP).
- **ships.toml** — per schematic: a human-readable display name (used in the UI), hull stats (HP, max linear speed, sensor range, main acceleration, maneuvering acceleration, angular acceleration, max rotation speed) as formulas of ship level, required build materials, player production level, the station level at which the schematic becomes available for unlock (`unlock_at_station_level`; -1 means the player starts with the schematic already unlocked), a layout grid defining the ship's module slots, a `scrap_drop` loot value, and a `default_modules` list used for enemy wave ships (see REQ-WAV-DEFAULT-MODULES).
- **modules.toml** — per module type: id, surface mask, materials list, initial player production level, production time, fill color, glyph, the station level at which the schematic becomes available for unlock (`unlock_at_station_level`; -1 means the player starts with the module schematic already unlocked), and an optional capability section and/or stat modifier formulas. A module with a capability section (`[module.weapon]`, `[module.salvage]`, or `[module.repair]`) containing base stat formulas is a **capability module** that grants the ship a weapon, salvage bay, or repair tool per instance (see REQ-MOD-CONFIG for the full list of formulas per capability type). A module with only `added_*`/`multiplied_*` formulas is a **passive module** that modifies stats on the ship or on capability module instances (see REQ-MOD-STAT-CALC).
- **stations.toml** — HP, damage, range, fire rate, and scrap drop for player and enemy defence stations, defined as formulas of station level.
- **visuals.toml** — rendering-only config (not game parameters): fill and outline colors and glyphs for every building type, item type, ship role, and station type; beam color and width; overlay and toast colors. Loaded by the UI at startup; the simulation does not read it.
- **visuals.toml** — rendering-only config (not game parameters): fill and outline colors and glyphs for every building type, item type, ship schematic, and station type; beam color and width; overlay and toast colors. Loaded by the UI at startup; the simulation does not read it.
- **ship_layouts.toml** — named layout blueprints per ship type; written and read by the application to persist the layout blueprint panel (REQ-MOD-UI-BLUEPRINT-PANEL through REQ-MOD-UI-BLUEPRINT-FILE-LOAD). Not a game parameter file; the simulation does not read it.
- REQ-CFG-RELOAD: When the player triggers a Restart (REQ-UI-GAME-MENU), all config files are reloaded from disk before the simulation is reset to its initial state. Formula strings are recompiled at that point. This allows config edits made while the application is running to take effect without a full application restart.
@@ -27,6 +29,42 @@ Buildings in buildings.toml define a `surface_mask` — a list of strings that d
Output port indicators are not building tiles themselves. A building may have more than one output port (e.g. a splitter uses `<A>` to declare both a left and a right output on the same tile).
### Ship Layout Format
Ships in `ships.toml` define a `layout` — a list of strings that describes the ship's module grid. Each character occupies one cell:
- `O` — buildable cell; modules can be placed here.
- `X` — non-buildable cell; not part of the ship's interior.
The layout grid determines which cells are available for module placement in the layout configuration dialog.
### Layout Blueprint TOML Format
Each entry in `ship_layouts.toml` represents a named layout blueprint:
```toml
[[blueprint]]
name = "Heavy Shields" # display name; must be unique within a ship type
ship_type = "fighter" # matches a schematic id in ships.toml
modules = [
{type = "shield_module", x = 0, y = 0, rotation = 0},
{type = "cannon_module", x = 2, y = 1, rotation = 90},
]
```
- `name` — human-readable display name. Must be unique within a ship type; need not be globally unique.
- `ship_type` — the schematic id this blueprint belongs to. Must match a schematic defined in `ships.toml`.
- `modules` — array of placed module instances. Each entry: `type` (module id from `modules.toml`), `x` and `y` (zero-based column/row in the ship's layout grid), `rotation` (0, 90, 180, or 270 degrees clockwise). If `modules` is absent or empty, the blueprint represents an empty layout.
The `modules` array format is reused verbatim in `balancing.toml` ship entries (see REQ-BAL-TEAM).
### Module Surface Mask Format
Modules in `modules.toml` define a `surface_mask` — a list of strings that describes the module's tile footprint within the ship layout grid. Each character occupies one cell:
- `O` — module cell: must be placed on an unoccupied buildable cell (`O`) of the ship's layout.
- `X` — ignored cell: may overlap any cell (non-buildable, unoccupied buildable, or occupied buildable) or extend outside the layout grid entirely.
## Game World
- REQ-GW-COORDS: Tile coordinates are integer `(x, y)`. The origin `(0, 0)` is the first column of space — the tile immediately to the right of the asteroid's right edge at game start, at the top of the world. X grows right; Y grows down. All asteroid tiles have `x < 0`; asteroid left-expansions add tiles at increasingly negative X. The origin never shifts.
@@ -69,14 +107,14 @@ Output port indicators are not building tiles themselves. A building may have mo
## Building Types
- REQ-BLD-MINER: **Miner** (2×2): The player selects which ore type it extracts. Each ore type corresponds to a `recipes.toml [[recipe]]` entry with `building = "miner"`, defining the output item and `duration_seconds`. Every asteroid tile is equivalent for mining — any miner can produce any ore type based solely on its selected recipe. Ore never depletes.
- REQ-BLD-MINER: **Miner** (2×2): The player selects which ore type it extracts. Each ore type corresponds to a `recipes.toml [[recipe]]` entry with `building = "miner"`, defining the output item and `duration_seconds`. Every asteroid tile is equivalent for mining — any miner can produce any ore type based solely on its selected recipe. Ore never depletes. Only implicitly unlocked ore-type recipes are available for selection (REQ-LOCK-UI-RECIPE).
- REQ-BLD-SMELTER: **Smelter** (2×2): Converts ore or scrap into basic materials. No recipe selection required. Inputs, outputs, and rates are defined in `recipes.toml [[recipe]]` entries with `building = "smelter"`.
- REQ-BLD-ASSEMBLER: **Assembler** (3×3): The player selects a recipe from the config-defined crafting tree. Produces the selected output item at the rate defined in the corresponding `recipes.toml [[recipe]]` entry with `building = "assembler"`.
- REQ-BLD-REPROCESSING: **Reprocessing Plant** (3×3): Consumes scrap per cycle and produces exactly one higher-level intermediate product per cycle via weighted random pick. The input quantity, possible output items, per-output weights, and amounts are defined in `recipes.toml [[recipe]]` entries with `building = "reprocessing_plant"` (`inputs`, `outputs[].item`, `outputs[].amount`, `outputs[].weight`). Weights are normalized at load time; their sum does not need to equal 1. The output is rolled at cycle start (see REQ-MAT-CYCLE). The output buffer holds at most one cycle's output — see REQ-MAT-OUTPUT-BUFFER-REPROCESSING.
- REQ-BLD-SHIPYARD: **Shipyard** (4×2): The player selects a schematic. When all required materials (`[ship.schematic].materials`) are present in its input buffer, the shipyard consumes them and begins a production cycle lasting `[ship.schematic].production_time_seconds` seconds (read from `ships.toml`). One ship of that type is spawned at `ships.toml [ship.schematic].player_production_level` (initial value 5, incremented by duplicate schematic drops per REQ-DEF-SCHEMATIC-DROP) when the cycle completes. The shipyard cannot start a new cycle while one is in progress.
- REQ-BLD-ASSEMBLER: **Assembler** (3×3): The player selects a recipe from the config-defined crafting tree. Produces the selected output item at the rate defined in the corresponding `recipes.toml [[recipe]]` entry with `building = "assembler"`. Only implicitly unlocked recipes are available for selection (REQ-LOCK-UI-RECIPE).
- REQ-BLD-REPROCESSING: **Reprocessing Plant** (3×3): Consumes scrap per cycle and produces exactly one higher-level intermediate product per cycle via weighted random pick. The input quantity, possible output items, per-output weights, and amounts are defined in `recipes.toml [[recipe]]` entries with `building = "reprocessing_plant"` (`inputs`, `outputs[].item`, `outputs[].amount`, `outputs[].weight`). Weights are normalized at load time; their sum does not need to equal 1. The output is rolled at cycle start (see REQ-MAT-CYCLE); the pool of eligible outputs is restricted to implicitly unlocked item types (REQ-LOCK-REPROCESSING-POOL). The output buffer holds at most one cycle's output — see REQ-MAT-OUTPUT-BUFFER-REPROCESSING.
- REQ-BLD-SHIPYARD: **Shipyard** (4×2): The player selects a schematic. When all required materials — the ship's base materials (`[ship.schematic].materials`) plus the materials of all modules in the configured layout (REQ-MOD-MATERIALS) — are present in its input buffer, the shipyard consumes them and begins a production cycle lasting the ship's base `[ship.schematic].production_time_seconds` plus the sum of production times contributed by all module instances in the configured layout (REQ-MOD-PRODUCTION-TIME). One ship of that type is spawned at `ships.toml [ship.schematic].player_production_level` (initial value 5, incremented by duplicate schematic drops per REQ-DEF-SCHEMATIC-DROP) with the configured modules when the cycle completes. The shipyard cannot start a new cycle while one is in progress. If the player confirms a layout change (REQ-MOD-UI-DIALOG) while a production cycle is in progress, the current cycle is cancelled and all consumed materials are discarded; the shipyard returns to idle with the new layout configuration.
- REQ-BLD-SALVAGE-BAY: **Salvage Bay** (3×2): A dedicated drop-off point for salvage ships. Scrap delivered here is placed onto connected output belts.
- REQ-BLD-BELT: **Belt** (1×1): Transports items. A belt tile has one direction (N, S, E, W) set at placement (modified by rotation). Curved belts are auto-derived: when a belt tile's outgoing direction leads into another belt whose direction is orthogonal, the downstream belt is rendered and behaves as a curve. Belt speed is defined in `world.toml [world].belt_speed_tiles_per_second` (REQ-GW-BELT-SPEED).
- REQ-BLD-SPLITTER: **Splitter** (1×1): Distributes incoming items between two output directions. Each output can optionally have a filter (a list of item types), configurable via the selected building panel. Routing rules:
- REQ-BLD-SPLITTER: **Splitter** (1×1): Distributes incoming items between two output directions. Each output can optionally have a filter (a list of item types), configurable via the selected building panel; only implicitly unlocked item types are available as filter options (REQ-LOCK-UI-SPLITTER). Routing rules:
- An item matching only one output's filter is routed to that output.
- An item matching both outputs' filters is distributed by strict alternation between those outputs.
- An item matching neither output's filter is routed to the unfiltered output. If both outputs have a filter and the item matches neither, the splitter stalls and moves no items until the situation is resolved.
@@ -111,23 +149,130 @@ Output port indicators are not building tiles themselves. A building may have mo
## Ships
- REQ-SHP-AUTONOMOUS: Ships are produced by shipyards and are fully autonomous once produced.
- REQ-SHP-STATS: All ship stats are defined as formulas of ship level in `ships.toml`: HP (`[ship.health].hp_formula`), speed (`[ship.movement].speed_formula`), damage (`[ship.combat].damage_formula`), attack range (`[ship.combat].attack_range_formula`), attack rate (`[ship.combat].attack_rate_formula`), sensor range (`[ship.sensors].range_formula`). Required build materials (`[ship.schematic].materials`) and availability from game start (`[[ship]].available_from_start`) are also defined there.
- REQ-SHP-STATS: Base hull stats are defined as formulas of ship level in `ships.toml`: HP (`[ship.health].hp_formula`), max linear speed (`[ship.movement].speed_formula`), sensor range (`[ship.sensors].range_formula`), main acceleration (`[ship.movement].main_acceleration_formula`, tiles/s²), maneuvering acceleration (`[ship.movement].maneuvering_acceleration_formula`, tiles/s²), angular acceleration (`[ship.movement].angular_acceleration_formula`, rad/s²), max rotation speed (`[ship.movement].max_rotation_speed_formula`, rad/s). Required build materials (`[ship.schematic].materials`) and the station level at which the schematic becomes available for unlock (`[[ship]].unlock_at_station_level`; -1 = player starts with the schematic already unlocked) are also defined there. Combat, salvage, and repair capabilities are provided by modules (see REQ-MOD-CONFIG). Final hull stats incorporate passive module modifiers per REQ-MOD-STAT-CALC.
- REQ-SHP-SPAWN-PLAYER: A ship produced by a shipyard spawns centered on the shipyard's output port tile.
- REQ-SHP-SPAWN-ENEMY: Enemy ships spawn at a uniformly random position within the current enemy buffer zone — random X across the buffer's width and random Y across the world height.
- REQ-SHP-MOVEMENT: Ships move in straight lines toward their current destination at the speed defined by their speed formula. Ship position refers to the ship's center for all range, sensor, and attack checks.
- REQ-SHP-MOVEMENT: Ships move using a physics-based model. Each ship has a velocity and a facing direction, both updated each tick. The main acceleration (`main_acceleration_formula`) is applied along the ship's current facing direction only. The maneuvering acceleration (`maneuvering_acceleration_formula`) can be applied in any direction independently of the facing direction, enabling lateral or braking movement without rotating. The angular acceleration (`angular_acceleration_formula`) controls how quickly the ship rotates. Linear speed is capped at the ship's `speed_formula` value; rotation rate is capped at the ship's `max_rotation_speed_formula` value. Ship position refers to the ship's center for all range, sensor, and attack checks.
- REQ-SHP-NO-COLLISION: Ships do not collide with each other or with defence stations; they may visually overlap.
- REQ-SHP-SENSOR: A ship perceives only entities within its sensor range. Behavior is driven by what is in sensor range; entities outside sensor range are ignored.
- REQ-SHP-FIRING: All weapons — on ships and on defence stations — fire when off cooldown and the target is within attack range. Firing emits a fire event and starts a 0.15-second damage delay (half the beam duration). When that delay expires, damage is applied to the target — unless the target has already been destroyed, in which case the damage is silently dropped. If the shooter is destroyed before the delay expires, damage is still applied when the delay expires. There is no projectile entity and no intervening collision. The weapon's cooldown begins at the moment of firing, not at damage application.
- REQ-SHP-FIRING-BEAM: Each fire event produces a visual laser beam drawn from the shooter's position to the target for 0.3 seconds. The beam endpoint is not the target's center but a point randomly offset from it: the offset direction is uniformly random and the offset magnitude is uniformly random up to half the target's visual size (for ships: half their rendered radius; for buildings/stations: half the shorter side of their tile footprint, in world units). The offset is chosen once per fire event and held fixed for the beam's lifetime. The beam is a pure rendering effect and has no simulation state (does not block movement, does not re-apply damage over its lifetime). Beams follow the shooter and target positions if either moves during the 0.3-second window. The beam is rendered for its full 0.3-second duration even if the shooter or target is destroyed before it expires.
- REQ-SHP-COMBAT: **Combat ships** (player) — engage enemy ships within sensor range. The player can configure the following per shipyard (applied to all ships produced by that shipyard):
- REQ-SHP-COMBAT: Ships with at least one **weapon module** (player) — engage enemy ships within sensor range. The player can configure the following per shipyard (applied to all ships produced by that shipyard):
- Stance: aggressive (advance toward enemies) / defensive (hold position near asteroid).
- Target priority: closest / highest HP / structures first.
- REQ-SHP-RALLY: After spawning, aggressive-stance combat ships move to and loiter at the **rally point** — the midpoint between the two player defence stations (center of their Y-span, at the player defence stations' X position). While at the rally point, ships still engage any enemy that enters sensor range. Every `world.toml [world].departure_interval_seconds` seconds (default 20), all combat ships currently at the rally point depart simultaneously and begin their normal aggressive advance toward the enemy. The departure timer is global and shared across all shipyards; it is not reset by individual ship arrivals at the rally point.
- REQ-SHP-SALVAGE: **Salvage ships** (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If scrap enters sensor range, move to it, collect, and deliver it to a Salvage Bay on the asteroid; after delivery, resume patrol. If an enemy ship enters sensor range while not currently targeting or carrying scrap, turn back (move toward the asteroid) until the enemy is no longer in sensor range, then resume patrol. Salvage ships are vulnerable to enemy ships while operating.
- REQ-SHP-REPAIR: **Repair ships** (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If a damaged player defence station or player ship enters sensor range, move to it and repair. If an enemy ship enters sensor range while not currently repairing, turn back (move toward the asteroid) until the enemy is no longer in sensor range, then resume patrol. The player can configure the target priority per shipyard:
- REQ-SHP-RALLY: After spawning, aggressive-stance ships with weapon modules move to and loiter at the **rally point** — the midpoint between the two player defence stations (center of their Y-span, at the player defence stations' X position). While at the rally point, ships still engage any enemy that enters sensor range. Every `world.toml [world].departure_interval_seconds` seconds (default 20), all ships with weapon modules currently at the rally point depart simultaneously and begin their normal aggressive advance toward the enemy. The departure timer is global and shared across all shipyards; it is not reset by individual ship arrivals at the rally point.
- REQ-SHP-SALVAGE: Ships with at least one **salvage module** (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If scrap enters sensor range, move to it; when it is within a module's `collection_range`, that module collects it (consuming the scrap entity). Once all cargo is full, fly to a Salvage Bay and deliver; after delivery, resume patrol. If an enemy ship enters sensor range while not currently targeting or carrying scrap, turn back (move toward the asteroid) until the enemy is no longer in sensor range, then resume patrol. Ships with salvage modules are vulnerable to enemy ships while operating.
Each salvage module instance operates independently: it has its own cargo hold (`cargo_capacity`), collection range (`collection_range`), and collection rate (`collection_rate`, in collections per second). After collecting a piece of scrap, the module cannot collect again until `1 / collection_rate` seconds have elapsed. A ship with multiple salvage modules can therefore collect multiple pieces of scrap per tick (one per ready module), and installs of different module types may have different ranges and rates. The ship navigates based on the maximum collection range across all installed salvage modules.
- REQ-SHP-REPAIR: Ships with at least one **repair module** (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If a damaged player defence station or player ship enters sensor range, move to it and repair. If an enemy ship enters sensor range while not currently repairing, turn back (move toward the asteroid) until the enemy is no longer in sensor range, then resume patrol. The player can configure the target priority per shipyard:
- Defence stations first / ships first / nearest target.
Each repair module instance operates independently: it has its own repair rate (`repair_rate`) and repair range (`repair_range`). On each tick, a module first attempts to heal the ship's current behavior-level navigation target if that target is within the module's `repair_range` and is damaged (HP above zero and below maximum HP). If those conditions are not met — because the target is out of the module's `repair_range`, already at full health, or destroyed — the module independently searches for the nearest damaged friendly (player ship or player defence station) within its own `repair_range` and heals that instead. If no valid target is found within range, the module idles. A ship with multiple repair modules can therefore heal different targets simultaneously. Navigation is driven solely by the behavior-level target; individual module fallback targets do not affect which direction the ship moves.
- REQ-SHP-ENEMY-AI: **Enemy ships** — engage the closest valid target (player defence station, HQ, or player ship) within their sensor range. If no target is in sensor range, they move toward the asteroid (leftward in world coordinates).
- REQ-SHP-SCHEMATICS: The player selects a schematic per shipyard by clicking it. New schematics are unlocked automatically when an enemy defence station set is destroyed (REQ-DEF-SCHEMATIC-DROP) — there is no physical loot to collect.
- REQ-SHP-SCHEMATICS: The player selects a schematic per shipyard by clicking it. New schematics are unlocked by destroying enemy defence station sets (REQ-DEF-SCHEMATIC-DROP) — there is no physical loot to collect.
## Ship Modules
### Module Configuration
- REQ-MOD-CONFIG: Module types are defined in `modules.toml`. Each module entry specifies:
- `id` — unique identifier, also used as the display name in the UI.
- `surface_mask` — footprint within the ship layout grid (see Module Surface Mask Format).
- `materials` — list of materials required per instance (added to the ship's build cost).
- `player_production_level` — initial level for this module type; used as `x` in its stat formulas. Incremented by 1 on each duplicate schematic drop (REQ-DEF-SCHEMATIC-DROP).
- `unlock_at_station_level` — the enemy defence station level at which this module's schematic becomes available for unlock; -1 means the player starts with the module schematic already unlocked.
- `production_time_seconds` — time added to the ship's production cycle per instance.
- `fill_color` — fill color used to render this module's cells in the layout grid.
- `glyph` — single character rendered on this module's cells in the layout grid and preview widget.
- An optional **capability section** (`[module.weapon]`, `[module.salvage]`, or `[module.repair]`) containing base stat formulas. A module with base stat formulas is a capability module — each placed instance grants the ship an independent weapon, salvage bay, or repair tool with its own state (cooldown, target, cargo). A ship may have multiple capability module instances of the same or different types. Base stat formulas per capability type:
- **Weapon** (`[module.weapon]`): `damage_formula`, `attack_range_formula`, `attack_rate_formula`.
- **Salvage** (`[module.salvage]`): `collection_range_formula` (tiles), `cargo_capacity_formula` (integer scrap units), `collection_rate_formula` (collections per second).
- **Repair** (`[module.repair]`): `repair_rate_formula` (HP/s), `repair_range_formula` (tiles).
- Zero or more **passive stat modifier formulas** (`added_*`/`multiplied_*`) that boost stats on the ship hull or on capability module instances (see REQ-MOD-STAT-CALC). A single module may be both a capability module and provide passive modifiers.
- REQ-MOD-LAYOUT: Each ship in `ships.toml` defines a `layout` — a list of strings representing the ship's module grid (see Ship Layout Format). All ships define a layout.
### Module Placement
- REQ-MOD-PLACEMENT: In the layout configuration dialog (REQ-MOD-UI-DIALOG), the player places modules onto the ship's layout grid. Clicking a module button in the module selection grid enters module placement mode for that module type. While in placement mode, a ghost of the module's surface mask is rendered at the cell under the cursor. Clicking a valid position places one instance of the module. A position is valid if every `O` cell in the module's (rotated) surface mask coincides with an unoccupied buildable cell of the ship's layout. The player may place unlimited instances of the same module type.
- REQ-MOD-ROTATION: While in module placement mode, pressing Q rotates the module ghost 90° counter-clockwise and E rotates it 90° clockwise. Rotation transforms the surface mask grid identically to building rotation (REQ-BLD-ROTATE).
- REQ-MOD-REMOVE: The module selection grid includes a "Remove" button. Clicking it enters remove mode. In remove mode, clicking on a cell occupied by a placed module removes that entire module instance from the layout. Remove mode is exited by clicking the Remove button again or by selecting a module for placement.
### Module Effects
- REQ-MOD-MATERIALS: The total materials required to build a ship are the union of the ship's base `[ship.schematic].materials` and the `materials` of every module instance in the configured layout. Quantities of the same item type are summed.
- REQ-MOD-PRODUCTION-TIME: The total production time is the ship's base `[ship.schematic].production_time_seconds` plus the sum of `production_time_seconds` for every module instance in the configured layout.
- REQ-MOD-THREAT: The threat cost of a ship is dynamically derived from the accumulated total production time required to produce that ship from scratch. One second of production time equals one threat. The total production time is the sum of:
1. The ship's base `production_time_seconds`.
2. The `production_time_seconds` of every module instance in the configured layout.
3. For every material required (the union of the ship's base materials and all module instance materials, with quantities summed per item type): the recursive production time of that material multiplied by the required quantity (see REQ-THREAT-ITEM).
- REQ-THREAT-ITEM: The threat value of an item type (in seconds) is determined by the recipe that produces it:
- **Miner recipe**: the recipe's `duration_seconds`.
- **Smelter recipe**: the recipe's `duration_seconds` plus the sum of each input's threat value multiplied by that input's required quantity.
- **Assembler recipe**: the recipe's `duration_seconds` plus the sum of each input's threat value multiplied by that input's required quantity.
- **Reprocessing-only item** (an item type that has no miner, smelter, or assembler recipe producing it, and is only obtainable via reprocessing): `(scrap_threat × scrap_per_cycle + duration_seconds) / probability`, where `scrap_threat` is the threat value of scrap (see REQ-THREAT-SCRAP), `scrap_per_cycle` is the number of scrap consumed per reprocessing cycle, `duration_seconds` is the reprocessing cycle time, and `probability` is the normalized weight of that item in the reprocessing output pool.
- **Multiple recipes**: if an item type can be produced by more than one non-reprocessing recipe (miner, smelter, or assembler), its threat value is the **maximum** across all such recipes. The reprocessing path is only used when no other recipe exists.
- REQ-THREAT-SCRAP: The threat value of scrap is derived from the ship schematic with the smallest configured `scrap_drop` value (from `ships.toml [ship.loot].scrap_drop`). Scrap threat = that ship's threat cost (REQ-MOD-THREAT) / that ship's `scrap_drop` value. If multiple schematics share the same smallest `scrap_drop`, any one of them may be used.
- REQ-MOD-STAT-CALC: For each stat (on the ship hull or on a capability module instance), the final value is computed as: `final = base × total_multiplier + total_additive`, where:
- `base` is the stat's base formula evaluated at the ship's production level (for hull stats) or at the capability module's `player_production_level` (for capability module stats).
- `total_multiplier` = 1 + sum of (m_i 1) for each multiplicative modifier m_i from all passive module instances. Each m_i is evaluated from the module's multiplicative formula at the module's `player_production_level`.
- `total_additive` = sum of all additive modifier values from all passive module instances. Each additive value is evaluated from the module's additive formula at the module's `player_production_level`.
Passive modifier formulas follow the naming convention: a module may define `added_<stat>_formula` (additive) and/or `multiplied_<stat>_formula` (multiplicative) under `[module.<category>]`. The category determines what the modifier targets:
- `[module.health]`, `[module.movement]`, `[module.sensor]` — modifiers apply to the ship hull's stats.
- `[module.weapon]` — modifiers apply to every weapon module instance on the ship.
- `[module.salvage]` — modifiers apply to every salvage module instance on the ship.
- `[module.repair]` — modifiers apply to every repair module instance on the ship.
Example: `[module.sensor].added_sensor_range_formula` adds to the ship's sensor range. `[module.weapon].multiplied_damage_formula` multiplies the damage of every weapon module instance on the ship.
### Module UI
- REQ-MOD-UI-PREVIEW: When a schematic is selected in a shipyard's selected building panel, a small non-interactive **ship layout preview** widget is shown below the schematic dropdown. The preview renders the ship's layout grid at a reduced scale: buildable cells without a module are shown as white, non-buildable cells are shown as black, and cells occupied by a module are shown in that module's `fill_color` with the module's `glyph` character. Below the preview, a "Configure" button is shown.
- REQ-MOD-UI-DIALOG: Clicking the "Configure" button opens the **layout configuration dialog** as a modal. While the dialog is open, the game is paused (speed set to 0×). On close, the game speed is restored to what it was before the dialog was opened.
The dialog contains:
- **Top**: The ship's layout grid rendered at full scale. Buildable cells are white; non-buildable cells are black. Placed modules are rendered with their `fill_color` and `glyph`. The ghost of the currently selected module is shown at the cursor position when in placement mode.
- **Left** (below the grid): The ship stats panel (see REQ-MOD-UI-STATS-PANEL).
- **Center** (below the grid): A grid of module selection buttons (one per **unlocked** module type; see REQ-DEF-SCHEMATIC-DROP) plus a "Remove" button. Each module button shows the module id and its glyph.
- **Right** (below the grid): The layout blueprint panel (see REQ-MOD-UI-BLUEPRINT-PANEL through REQ-MOD-UI-BLUEPRINT-FILE-LOAD).
- **Bottom**: A "Confirm" button and a "Cancel" button. Cancel discards all changes made in this dialog session and closes the dialog. Confirm applies the changes: the shipyard's configured layout is updated, the required materials and cycle time displayed in the selected building panel are recalculated, and the ship layout preview is refreshed.
- REQ-MOD-UI-STATS-PANEL: The **ship stats panel** in the layout configuration dialog shows the stats of the currently configured ship layout as they would be computed at the schematic's `player_production_level`, incorporating all passive module modifiers per REQ-MOD-STAT-CALC. The panel updates in real time whenever modules are placed or removed in the layout grid.
The panel always shows all hull stats as final computed values:
- HP
- Max linear speed
- Sensor range
- Main acceleration
- Maneuvering acceleration
- Angular acceleration
- Max rotation speed
In addition, the panel shows capability module stats conditioned on which capability module types are present in the current layout:
- **Weapons** (shown only if at least one weapon module is placed): combined DPS = Σ(damage_i × attack_rate_i) across all weapon module instances; maximum range = max(attack_range_i) across all weapon module instances.
- **Salvage** (shown only if at least one salvage module is placed): combined collection rate = Σ(collection_rate_i) across all salvage module instances; maximum range = max(collection_range_i) across all salvage module instances.
- **Repair** (shown only if at least one repair module is placed): combined repair rate = Σ(repair_rate_i) across all repair module instances; maximum range = max(repair_range_i) across all repair module instances.
All capability module stat values incorporate passive modifiers targeting the relevant capability category per REQ-MOD-STAT-CALC. Each capability module instance uses its own `player_production_level` for formula evaluation.
While debug draw mode is active (REQ-UI-DEBUG-DRAW), the panel additionally shows the ship's derived threat cost (REQ-MOD-THREAT) for the current layout configuration. This value updates in real time as modules are placed or removed.
- REQ-MOD-UI-LAYOUT-SIZE: Ship layouts are small enough to display in the layout configuration dialog without scrolling (maximum grid size fits within the dialog).
### Layout Blueprints
- REQ-MOD-UI-BLUEPRINT-PANEL: The right column of the layout configuration dialog is the layout blueprint panel. It shows only blueprints whose `ship_type` matches the schematic of the shipyard for which the dialog was opened. The panel contains, from top to bottom: a "Create Blueprint" button, followed by a scrollable list of blueprint entries (one per matching blueprint, in creation order).
- REQ-MOD-UI-BLUEPRINT-CREATE: Clicking "Create Blueprint" opens a modal dialog prompting for a name. The dialog has Confirm and Cancel buttons. Clicking Cancel closes the dialog with no effect. Clicking Confirm with a non-empty name creates a blueprint from the module layout currently shown in the left-side layout grid (the in-progress state of the dialog, not the previously confirmed shipyard layout) and appends it to the blueprint list.
- REQ-MOD-UI-BLUEPRINT-ENTRY: Each blueprint entry shows the blueprint name and a delete icon ("×") to the right of the name. Clicking the entry (name area) loads that blueprint's module list into the left-side layout grid, replacing all currently placed modules. Module instances that are invalid for the current ship layout (unknown module type, locked module type, position outside the grid, position on a non-buildable cell, or overlapping another module in the same blueprint) are silently skipped; the remaining valid instances are placed. Clicking the delete icon ("×") removes that blueprint entry from the list immediately.
- REQ-MOD-UI-BLUEPRINT-STARTUP: At application startup, layout blueprints are loaded from `ship_layouts.toml` in the same directory as the application executable. Blueprint entries missing required fields (`name` or `ship_type`) are silently skipped. If the file does not exist, the blueprint list starts empty with no error. If the file exists but cannot be parsed (malformed TOML), a modal error dialog describes the failure and the blueprint list starts empty.
- REQ-MOD-UI-BLUEPRINT-SHUTDOWN: On application shutdown, all layout blueprints (across all ship types) are written to `ship_layouts.toml` in the same directory as the application executable. The TOML structure matches the Layout Blueprint TOML Format. Write errors are silently ignored on shutdown.
## Defence Stations
@@ -137,20 +282,62 @@ Output port indicators are not building tiles themselves. A building may have mo
- REQ-DEF-ENEMY-PLACEMENT: 2 enemy defence stations are placed at the right boundary of the scrollable area at game start, and again each time a new set is spawned after a push. Stats scale with the station level (REQ-PSH-STATION-STATS).
- REQ-DEF-ENEMY-FIRE: Enemy defence stations automatically fire at player ships within range.
- REQ-DEF-NO-CROSSFIRE: Enemy and player defence stations are never in each other's firing range.
- REQ-DEF-PUSH: When both enemy defence stations in a set are destroyed, the push scaling multiplier is applied (REQ-PSH-ACCUMULATION), the scrollable area is extended (REQ-GW-PUSH-EXPAND), a new set of enemy defence stations is placed at the new boundary, and exactly one schematic drop is awarded for the destroyed set (REQ-DEF-SCHEMATIC-DROP).
- REQ-DEF-SCHEMATIC-DROP: Each destroyed set of enemy defence stations awards exactly one schematic drop (not one per station). The drop is automatic — no physical item to collect. A schematic is chosen uniformly at random from all schematics defined in `ships.toml`. If the player does not yet have that schematic, it is unlocked. If the player already has it, the schematic's `[ship.schematic].player_production_level` is incremented by 1 — so subsequent ships of that type are produced at a higher level. The player is notified via a toast (REQ-UI-SCHEMATIC-TOAST).
- REQ-DEF-PUSH: When both enemy defence stations in a set are destroyed, the boss countdown is advanced (REQ-WAV-BOSS-ADVANCE), the scrollable area is extended (REQ-GW-PUSH-EXPAND), a new set of enemy defence stations is placed at the new boundary, and exactly one schematic drop is awarded for the destroyed set (REQ-DEF-SCHEMATIC-DROP).
- REQ-DEF-SCHEMATIC-DROP: Each destroyed set of enemy defence stations awards exactly one schematic drop (not one per station). The drop opens a **schematic choice dialog** — a modal dialog that pauses the game (speed set to 0×; on close, speed is restored to what it was before the dialog opened). Up to three schematic options are drawn uniformly at random **without replacement** from the eligible drop pool. If the pool contains fewer than three entries, only that many options are shown. The eligible drop pool contains:
- All **ship schematics** and **module schematics** whose `unlock_at_station_level` is -1 or is ≤ the level of the destroyed station set.
- All **assembler recipe schematics** whose `unlock_at_station_level` is ≥ 0 and ≤ the level of the destroyed station set, whose output item is currently implicitly unlocked (REQ-LOCK-IMPLICIT), and which have not yet been awarded.
Each option in the dialog displays: the schematic name (ship `display_name` from `ships.toml`, module `id` from `modules.toml`, or the output item type for assembler recipes), the schematic type (ship, module, or assembler recipe), and whether selecting it would be a **new unlock** or a **level-up** (showing the target level for level-ups). Assembler recipe schematics are always new unlocks since they are removed from the pool once awarded.
Each option additionally displays a vertical list of item names labeled "Unlocks recipes for:", showing which recipes would newly become implicitly unlocked (REQ-LOCK-IMPLICIT) if this option were selected — specifically, the output items of miner recipes and assembler recipes (without `unlock_at_station_level`) that are not currently implicitly unlocked but would become so after applying this option's effect:
- For a ship or module schematic that would be a **new unlock**, its `materials` are added to the base set per REQ-LOCK-IMPLICIT step 1a before recomputation.
- For a ship or module schematic **level-up**, the implicit unlock set is unchanged, so the list is always empty.
- For an assembler recipe schematic, its output item is added to the base set per REQ-LOCK-IMPLICIT step 1b before recomputation.
Item names are deduplicated and sorted alphabetically. If no recipes would be newly unlocked, the list shows "None".
The player selects one option by clicking it. The selected schematic is applied and the dialog closes:
For a **ship or module schematic**: if the player does not yet have the schematic, it is unlocked (ship schematics unlock the corresponding shipyard selection; module schematics unlock the module type for placement in the layout configuration dialog (REQ-MOD-UI-DIALOG)). If the player already has it, the schematic's `player_production_level` is incremented by 1 — for ship schematics, subsequent ships of that type are produced at a higher level; for module schematics, all instances of that module type use the higher level in their stat formulas.
For an **assembler recipe schematic**: the recipe is explicitly unlocked and becomes available in the assembler recipe-selection dropdown (subject to REQ-LOCK-UI-RECIPE). The schematic is removed from the drop pool permanently (REQ-LOCK-EXPLICIT). The implicit unlock set is recomputed (REQ-LOCK-IMPLICIT).
## Progression & Locking
- REQ-LOCK-EXPLICIT: Ship schematics, module schematics, and **assembler recipe schematics** (assembler recipes in `recipes.toml` that define `unlock_at_station_level`) are **explicitly** locked or unlocked. A schematic starts unlocked if its `unlock_at_station_level` is -1; all others start locked. Locked schematics are unlocked only by REQ-DEF-SCHEMATIC-DROP. Once unlocked, a schematic is never re-locked within a run; lock states reset to their initial values on Restart (REQ-CFG-RELOAD). Unlike ship and module schematics, an assembler recipe schematic is removed from the drop pool permanently once awarded and cannot be dropped again.
- REQ-LOCK-IMPLICIT: Item types and miner/assembler recipes are **implicitly** unlocked or locked based on the current set of unlocked ship, module, and assembler recipe schematics. The implicit unlock set is recomputed whenever any schematic changes lock state (on Restart or after REQ-DEF-SCHEMATIC-DROP). Computation:
1. Start with the union of: (a) all item types listed in `materials` across all currently unlocked ship schematics and all currently unlocked module schematics, and (b) the output item type of every currently explicitly unlocked assembler recipe schematic (REQ-LOCK-EXPLICIT).
2. For each item type in the current set: for every recipe (miner, smelter, or assembler) that produces it — skipping any assembler recipe schematic that defines `unlock_at_station_level` and is not yet explicitly unlocked — add each of that recipe's input item types to the set. If the recipe is a miner recipe or an assembler recipe that does not define `unlock_at_station_level`, mark it as implicitly unlocked. Explicitly unlocked assembler recipe schematics are available in the assembler dropdown by virtue of REQ-LOCK-EXPLICIT; their inputs are also added to the implicit set in this step.
3. Repeat step 2 until no new item types are added.
Item types and miner/assembler recipes not reached by this process (and not explicitly unlocked) are locked. Smelter recipes participate in the traversal to propagate unlocking to their inputs but are never themselves shown in any UI dropdown.
- REQ-LOCK-REPROCESSING-POOL: The pool of possible outputs for a Reprocessing Plant cycle (REQ-BLD-REPROCESSING) is restricted to item types that are currently implicitly unlocked (REQ-LOCK-IMPLICIT). Weights are renormalized over the eligible outputs. If no eligible outputs remain, the Reprocessing Plant cannot start a production cycle.
- REQ-LOCK-UI-RECIPE: Locked miner ore-type recipes and assembler recipes are not shown in their respective recipe-selection dropdowns.
- REQ-LOCK-UI-SCHEMATIC: Locked ship schematics are not shown in the shipyard's schematic-selection dropdown.
- REQ-LOCK-UI-SPLITTER: Item types that are not implicitly unlocked are excluded from splitter filter dropdowns (REQ-BLD-SPLITTER).
- REQ-LOCK-UI-BLUEPRINT: When a blueprint is placed (REQ-UI-BLUEPRINT-PLACE): if a stored recipe ID for a miner or assembler is currently locked, that building's recipe is left unset rather than applied; if a stored splitter filter entry refers to a locked item type, that entry is silently removed. (The analogous rule for locked ship schematics is defined in REQ-UI-BLUEPRINT-PLACE.)
## Threat Level & Enemy Waves
- REQ-WAV-THREAT-RATE: A global **threat level** accumulates continuously over time. The rate of increase per second is determined by `world.toml [waves].threat_rate_formula` where x is elapsed game time in seconds, clamped to a minimum of 0 (negative formula values are treated as 0). Example: `1*x - 30` yields 0 threat/s for x ≤ 30s and increases linearly after that.
- REQ-WAV-GAP: At game start and immediately after each wave is triggered, a random inter-wave gap is drawn uniformly from [`world.toml [waves].gap_min_seconds`, `gap_max_seconds`].
- REQ-WAV-TRIGGER: When the gap expires, a wave is triggered. Ships are selected one at a time: from all schematics whose `threat.cost_formula` evaluates to > 0 at the current enemy ship level, uniformly randomly pick one whose cost fits the remaining threat budget. Repeat until no eligible schematic fits. Any remaining threat carries over to the next wave. A longer gap results in a larger wave. Because enemy ship level increases with time (REQ-WAV-SHIP-LEVEL), threat cost per ship rises naturally over the course of the game.
- REQ-WAV-SHIP-LEVEL: Each wave's enemy ships are assigned a level determined by `world.toml [waves].ship_level_formula` where x is elapsed game time in seconds. This is the sole mechanism by which individual enemy ships become stronger over time. Wave *size* grows separately via threat accumulation (REQ-WAV-THREAT-RATE) and push scaling (REQ-PSH-ACCUMULATION). Per-ship stats and threat cost are computed from the ship level via the formulas in `ships.toml` (see REQ-SHP-STATS).
- REQ-WAV-BOSS-COUNTER: A global **boss wave counter** `x` starts at 1 at game start and increments by 1 immediately after each boss wave fires. It represents the current boss wave cycle number and is used as the variable in the threat rate and ship level formulas.
- REQ-WAV-THREAT-RATE: A global **threat level** accumulates continuously over real game time. The rate of increase per second is determined by `world.toml [waves].threat_rate_formula` where `x` is the boss wave counter (REQ-WAV-BOSS-COUNTER), clamped to a minimum of 0 (negative formula values are treated as 0). The rate is constant within each boss wave cycle and steps up each time `x` increments. Threat accumulation is paused during quiet windows (REQ-WAV-QUIET). Example: `1*x - 30` yields 0 threat/s when x ≤ 30 and increases linearly beyond that.
- REQ-WAV-GAP: At game start and immediately after each normal wave is triggered, a random inter-wave gap is drawn uniformly from [`world.toml [waves].gap_min_seconds`, `gap_max_seconds`]. The gap timer does not advance while inside a quiet window (REQ-WAV-QUIET); if a gap would expire inside a quiet window, its expiry is deferred until the quiet window ends.
- REQ-WAV-TRIGGER: When the gap timer expires outside a quiet window, a normal wave is triggered. Ships are selected one at a time: from all schematics whose threat cost (REQ-MOD-THREAT) is > 0, uniformly randomly pick one whose cost fits the remaining threat budget. For wave ship selection, the threat cost is computed using the schematic's `default_modules` layout (REQ-WAV-DEFAULT-MODULES). Repeat until no eligible schematic fits. Any remaining threat carries over to the next normal wave. A longer gap results in a larger wave.
- REQ-WAV-SHIP-LEVEL: Each wave's (normal and boss) enemy ships are assigned a level determined by `world.toml [waves].ship_level_formula` where `x` is the boss wave counter (REQ-WAV-BOSS-COUNTER). Per-ship stats are computed from the ship level via the formulas in `ships.toml` (see REQ-SHP-STATS). Threat cost is level-independent (REQ-MOD-THREAT).
- REQ-WAV-BOSS-COUNTDOWN: A **boss countdown** timer starts at `world.toml [waves].boss_countdown_seconds` (default 300) at game start and counts down continuously in real game-time seconds. It is not paused during quiet windows. When it reaches 0, a boss wave is triggered (REQ-WAV-BOSS-TRIGGER). Immediately after the boss wave fires, `x` increments (REQ-WAV-BOSS-COUNTER) and a fresh countdown starts at the same configured value.
- REQ-WAV-BOSS-ADVANCE: When the player destroys a set of enemy defence stations, the boss countdown is reduced by `world.toml [push].boss_advance_seconds` (default 60), clamped to a minimum of 0. Threat that would have accumulated during the skipped time is not added. If the countdown reaches 0 by this reduction, the boss wave is triggered immediately.
- REQ-WAV-QUIET: A **quiet window** suppresses normal wave spawning around each boss wave. The pre-boss quiet window begins when the boss countdown falls to or below `world.toml [waves].boss_quiet_before_seconds` and ends when the countdown reaches 0. The post-boss quiet window begins immediately when the boss wave fires and lasts `world.toml [waves].boss_quiet_after_seconds` seconds. Threat accumulation is paused during both windows. The normal wave gap timer does not advance during either window (REQ-WAV-GAP). The new boss countdown runs during the post-boss quiet window.
- REQ-WAV-BOSS-TRIGGER: When the boss countdown reaches 0, a boss wave is triggered. Its threat budget is the sum of: (a) `world.toml [waves].boss_threat_duration_seconds` (default 60) multiplied by the current threat rate, and (b) all unspent threat carried over from normal waves. Ships are selected using the same random process as normal waves (REQ-WAV-TRIGGER). Any threat remaining unspent after ship selection carries over to the first normal wave of the new cycle.
- REQ-WAV-DEFAULT-MODULES: Enemy ships spawned by waves use the `default_modules` list defined per schematic in `ships.toml`. The `default_modules` array uses the same format as layout blueprints (see Layout Blueprint TOML Format). If `default_modules` is absent or empty, the ship spawns with no modules. Invalid module instances (unknown type, position outside the grid, position on a non-buildable cell, or overlapping another module) are silently skipped.
- REQ-WAV-SPAWN-DURATION: Ships in a wave are spawned one at a time over `world.toml [waves].spawn_duration_seconds`.
## Push Scaling
## Push Effects
- REQ-PSH-ACCUMULATION: Each time the player destroys a set of enemy defence stations, `world.toml [push].scaling_factor` is multiplied permanently into the threat level accumulation rate. Scaling factors stack multiplicatively with each other and with the time-based threat formula, causing all subsequent waves to be larger.
- REQ-PSH-STATION-STATS: Enemy defence station stats are each defined as formulas in `stations.toml [enemy_station]`: `hp_formula`, `damage_formula`, `range_formula`, `fire_rate_formula`, `scrap_drop_formula`, where x is the station level — an integer starting at 0 for the initial set and incrementing by 1 each time a new set is placed.
## Asteroid Expansion
@@ -162,38 +349,38 @@ Output port indicators are not building tiles themselves. A building may have mo
### Layout
The screen is divided into three vertical sections:
The screen is divided into two columns: a main column (75% width) containing the header bar and game world, and a side panel column (25% width) containing the three UI panels stacked vertically:
```
+--------------------------------------------------+
| Header Bar |
+--------------------------------------------------+
| |
| Game World (70%) |
| |
+-----------------+-----------------+--------------+
| Selected | Build Button | Blueprint |
| Building Panel | Grid | Panel |
| (left) | (center) | (right) |
+-----------------+-----------------+--------------+
+--------------------------------------+--------------+
| Header Bar | |
+--------------------------------------+ Selected |
| | Building |
| | Panel |
| +--------------+
| Game World | Build |
| | Button |
| | Grid |
| +--------------+
| | Blueprint |
| | Panel |
+--------------------------------------+--------------+
(75% width) (25% width)
```
- REQ-UI-HEADER: The header bar spans the full width above the game world and always shows the elapsed survival time and the current global building blocks stock on the left, and game speed controls on the right.
- REQ-UI-HEADER: The header bar spans the width of the game world column (75% of the screen width) and always shows the elapsed survival time and the current global building blocks stock on the left, the boss wave counter and boss countdown (REQ-UI-BOSS-STATUS) to the left of the speed buttons, and game speed controls on the right.
- REQ-UI-BOSS-STATUS: The header bar displays, to the left of the speed buttons, the current boss wave counter (REQ-WAV-BOSS-COUNTER) and the time remaining on the boss countdown (REQ-WAV-BOSS-COUNTDOWN). The boss wave counter is shown as `Boss Wave #<x>` and the countdown as `Next boss: <M:SS>`, where `<M:SS>` is the remaining seconds formatted as whole minutes and two-digit seconds. Both values update continuously as the simulation runs.
- REQ-UI-SPEED: The game speed controls in the header bar are buttons for 0×, 0.5×, 1×, 2×, and 4× speed. The currently active speed is shown as selected. All game simulation (production, movement, threat accumulation, wave timing) scales with the selected speed. 0× pauses the game.
- REQ-UI-WORLD-HEIGHT: The game world view occupies 70% of the remaining screen height below the header bar.
- REQ-UI-PANEL-HEIGHT: The UI panel occupies the remaining 30% of the screen height, split horizontally into a selected building panel (left), a build button grid (center), and a blueprint panel (right).
- REQ-UI-WORLD-SIZE: The game world view occupies the full height below the header bar in the main column (75% of the screen width).
- REQ-UI-PANEL-COLUMN: The side panel column occupies 25% of the screen width and the full screen height. It is divided into three equal-height panels stacked top to bottom: selected building panel (top), build button grid (middle), and blueprint panel (bottom).
### Game World
- REQ-UI-SCROLL: The player scrolls the view horizontally across the scrollable area by pressing A (scroll left) and D (scroll right).
- REQ-UI-CONSTRUCTION-PROGRESS: Construction sites display the building's glyph centered on the footprint (same as an operational building). Below the glyph — or centered on the footprint if the building has no glyph — a construction progress percentage is shown (integer, e.g. `42%`), increasing from 0% to 100% as construction completes.
- REQ-UI-PORT-GLYPH: Every output port of every building is indicated by a directional glyph drawn on the port's tile. The glyph is a `>` rotated to face the port's exit direction (`>` for East, `^` for North, `<` for West, `v` for South). It is drawn at the midpoint between the tile center and the tile edge that the port exits through (i.e. halfway from center toward the exit edge). The indicator is rendered for all building states: operational buildings, construction sites, and the builder-mode ghost. Buildings with multiple output ports (e.g. splitters) show one indicator per port.
- REQ-UI-HP-BARS: All entities with HP — the HQ, player and enemy defence stations, and player and enemy ships — render an HP bar below them. The bar is always visible regardless of current HP. The bar's filled portion represents the fraction of current HP to maximum HP.
- REQ-UI-NO-ZOOM: The view has a fixed zoom level; the player cannot zoom in or out.
- REQ-UI-SCHEMATIC-TOAST: When a schematic is unlocked or leveled up (REQ-DEF-SCHEMATIC-DROP), a transient notification toast appears in the top-right corner of the game world view for 4 seconds and then fades out. `<Ship Name>` in the text below is the schematic's `ships.toml [ship.schematic].display_name`. Toast text:
- **New unlock**: `Schematic unlocked: <Ship Name>`
- **Level-up (duplicate drop)**: `<Ship Name> production level → N` (where N is the new level).
If multiple toasts arrive in close succession, they stack vertically in a queue (most recent at the top) and each fades out independently after its own 4-second lifetime.
- REQ-UI-HOTKEYS: Global keyboard shortcuts:
- **Space** — toggles pause. Pressing Space pauses (sets speed to 0×) and stores the previously selected non-zero speed; pressing Space again restores that speed.
- **W** — increases game speed by one step in the sequence 0×, 0.5×, 1×, 2×, 4× (no wrap-around past 4×).
@@ -205,7 +392,14 @@ The screen is divided into three vertical sections:
### Debug Draw
- REQ-UI-DEBUG-DRAW: A debug draw mode can be toggled on and off with the **M** key (REQ-UI-HOTKEYS). It is inactive by default. While active, the sensor range of every ship — both player and enemy — is drawn as a circle centered on the ship, using that ship role's outline color from `visuals.toml`.
- REQ-UI-DEBUG-DRAW: A debug draw mode can be toggled on and off with the **M** key (REQ-UI-HOTKEYS). It is inactive by default. While active, the sensor range of every ship — both player and enemy — is drawn as a circle centered on the ship, using that ship schematic's outline color from `visuals.toml`.
- REQ-UI-DEBUG-OVERLAY: While debug draw mode is active (REQ-UI-DEBUG-DRAW), a text overlay is drawn in the upper left corner of the game world view. The overlay has a semi-transparent black background sized to fit its content. It displays the following lines of text:
- `Accumulated Threat Level: <level>` — where `<level>` is the current accumulated threat level (see REQ-WAV-THREAT-RATE).
- `Time until Wave: <time_s>` — where `<time_s>` is the remaining time in seconds on the normal-wave inter-wave gap timer (see REQ-WAV-GAP). During a quiet window the gap timer is frozen; the displayed value reflects that frozen state.
- `Threat Accumulation Rate: <rate> threat/s` — the rate at which the accumulated threat level is currently increasing (see REQ-WAV-THREAT-RATE). During a quiet window (REQ-WAV-QUIET), this is 0, reflecting that accumulation is currently paused.
- `Max Factory Production: <rate> threat/s` — the threat-equivalent of the factory's total possible production: 1 threat/second for each completed (operational, not under construction) miner, smelter, assembler, reprocessing plant, and shipyard. One second of production equals one threat (see REQ-MOD-THREAT).
- `Current Factory Production: <rate> threat/s` — the threat-equivalent of the factory's current production: 1 threat/second for each completed miner, smelter, assembler, reprocessing plant, or shipyard that currently has an active production cycle (see REQ-MAT-CYCLE; for shipyards, an in-progress production cycle per REQ-BLD-SHIPYARD).
### Escape Menu
@@ -222,8 +416,11 @@ The screen is divided into three vertical sections:
- REQ-UI-PRODUCTION-PROGRESS: For buildings that produce items or ships (miner, smelter, assembler, reprocessing plant, shipyard), the selected building panel also shows: (a) the cycle time of the currently selected recipe or schematic in seconds, and (b) the completion percentage of the active production cycle as an integer (e.g. `42%`), or the text `idle` when no production cycle is active. When no recipe or schematic is selected, neither the cycle time nor the progress indicator is shown.
- REQ-UI-MULTI-SELECT: The player selects multiple buildings by box-drag or by Ctrl+clicking individual buildings to add or remove them from the selection.
- REQ-UI-MULTI-SELECTION: When multiple buildings are selected, the panel shows how many of each building type are selected. No per-building detail is shown.
- REQ-UI-CONFIG-INLINE: Recipe, schematic, ship stance, and target priority configuration for a selected building is shown and changed inline within this panel.
- REQ-UI-CONFIG-INLINE: Recipe, schematic, ship stance, and target priority configuration for a selected building is shown and changed inline within this panel. For shipyards, the panel additionally shows the ship layout preview and "Configure" button below the schematic dropdown (REQ-MOD-UI-PREVIEW).
- REQ-UI-BELT-CLEAR: When one or more belt, splitter, tunnel entry, or tunnel exit tiles are selected, the panel shows a "Clear" button that removes all items from the selected tiles. Clearing a tunnel entry or exit also discards all items currently in transit through that tunnel (REQ-BLD-TUNNEL-TRANSIT). This can be used to resolve stalled belts, splitters, and tunnels.
- REQ-UI-ENTITY-CLICK-SELECT: The player can click any ship (player or enemy) or any defence station (player or enemy) in the game world to select it. Clicking a ship or defence station clears any existing selection and establishes a single-entity selection containing only that entity. Ships and defence stations cannot participate in multi-select together with buildings. Clicking empty world space (no building, ship, or defence station) clears the selection.
- REQ-UI-SHIP-STATS-PANEL: When a single ship is selected (REQ-UI-ENTITY-CLICK-SELECT), the selected building panel shows a **ship stats panel**. The panel structure mirrors REQ-MOD-UI-STATS-PANEL but reflects the ship's actual live state: stats are computed at the ship's actual level with its installed modules per REQ-MOD-STAT-CALC. The panel always shows all hull stats: HP (current / maximum), max linear speed, sensor range, main acceleration, maneuvering acceleration, angular acceleration, and max rotation speed. In addition, capability module summaries are shown conditioned on which module types are installed, using the same aggregation rules as REQ-MOD-UI-STATS-PANEL: weapons (combined DPS, maximum range), salvage (combined collection rate, maximum range), and repair (combined repair rate, maximum range), each section appearing only if at least one instance of that module type is installed. While debug draw mode is active (REQ-UI-DEBUG-DRAW), the panel additionally shows the ship's derived threat cost (REQ-MOD-THREAT).
- REQ-UI-STATION-STATS-PANEL: When a single defence station is selected (REQ-UI-ENTITY-CLICK-SELECT), the selected building panel shows a **station stats panel** displaying the station's stats computed at its current level: HP (current / maximum), damage, range, and fire rate.
### Build Button Grid
@@ -244,10 +441,53 @@ The screen is divided into three vertical sections:
- REQ-UI-BLUEPRINT-MODE: In blueprint placement mode a ghost is rendered for every building in the blueprint at the position determined by its stored tile offset from the bounding-box center, which is anchored to the tile under the cursor. Each ghost is rendered individually as valid or invalid, applying REQ-BLD-PLACE-VALID conditions (a) and (b) per building (the other ghosts in the same blueprint do not count as existing buildings for the overlap check). Pressing Q/E rotates the entire constellation 90° counter-clockwise / clockwise: each building's tile offset is rotated around the bounding-box center and each building's own rotation is updated, consistent with REQ-BLD-ROTATE. Blueprint placement mode is exited by right-clicking in the game world. Clicking a different blueprint button exits the current mode and enters blueprint placement mode for the newly clicked blueprint.
- REQ-UI-BLUEPRINT-PLACE: Left-clicking in blueprint placement mode places the blueprint if (a) every building in the constellation satisfies REQ-BLD-PLACE-VALID conditions (a) and (b) at its resolved tile, and (b) the player has enough building blocks to afford the total cost. If both conditions are met, a construction site is added to the build queue for each building in the blueprint and the full total cost is deducted from the global building blocks stock in one transaction. If a recipe ID is stored for a building, it is applied to the construction site immediately. If a schematic ID is stored, it is applied only if that schematic is currently unlocked; if it is not unlocked, the shipyard's schematic is left unset. After a successful placement the game remains in blueprint placement mode, allowing the player to place the same blueprint again immediately.
- REQ-UI-BLUEPRINT-PLACE: Left-clicking in blueprint placement mode places the blueprint if (a) every building in the constellation satisfies REQ-BLD-PLACE-VALID conditions (a) and (b) at its resolved tile, and (b) the player has enough building blocks to afford the total cost. If both conditions are met, a construction site is added to the build queue for each building in the blueprint and the full total cost is deducted from the global building blocks stock in one transaction. If a recipe ID is stored for a building, it is applied to the construction site immediately. If a schematic ID is stored, it is applied only if that schematic is currently unlocked; if it is not unlocked, the shipyard's schematic is left unset. Locked recipe IDs and splitter filter entries for locked item types are handled on placement per REQ-LOCK-UI-BLUEPRINT. After a successful placement the game remains in blueprint placement mode, allowing the player to place the same blueprint again immediately.
- REQ-UI-BLUEPRINT-DELETE: Clicking the delete icon ("×") on a blueprint entry immediately removes that blueprint from the list. If the deleted blueprint was active in blueprint placement mode, that mode is exited.
- REQ-UI-BLUEPRINT-SAVE: A "Save" button is shown at the bottom of the blueprint panel. Clicking it serializes all current blueprints to a file named `blueprints.toml` located in the same directory as the application executable. The TOML structure matches REQ-UI-BLUEPRINT-STORAGE. If writing fails, a modal error dialog is shown describing the failure.
- REQ-UI-BLUEPRINT-LOAD: A "Load" button is shown at the bottom of the blueprint panel, to the right of the "Save" button. Clicking it shows a confirmation dialog ("Load blueprints? This will replace all current blueprints.") with Confirm and Cancel buttons. Clicking Cancel closes the dialog with no effect. Clicking Confirm reads `blueprints.toml` from the same directory as the application executable, replaces all current blueprints with those from the file (in the order they appear in the file), and exits any active blueprint-related mode (blueprint placement mode, delete mode). If the file does not exist or cannot be parsed, a modal error dialog is shown describing the failure and the current blueprint list is left unchanged.
## Balancing Tool
A separate executable target (`balancing`) that links against `lib` but contains no main-game UI code. It provides an automated arena-based ship balancing tool. Code written exclusively for this target does not go into `lib`.
### Config
- REQ-BAL-CONFIG: The balancing tool reads arena definitions from a `balancing.toml` file. The file is read at startup and again each time the player triggers a config reload (REQ-BAL-UI-RELOAD). If parsing fails or required fields are missing at startup, the tool aborts with a clear error message. If parsing fails during a reload, a modal error dialog is shown describing the failure and the current arena list is left unchanged.
- REQ-BAL-CONFIG-GAME: Ship stats are read from `ships.toml` and defence station stats are read from `stations.toml`, using the same config loading as the main game. Formula evaluation uses the levels specified in the arena config.
### Arena Definition
- REQ-BAL-ARENA: Each arena in `balancing.toml` defines:
- A human-readable **arena name**.
- **Region widths** (in tiles): player buffer width, contest zone width, enemy buffer width. The arena world is pure space — no asteroid region.
- **World height** (in tiles).
- Exactly **two teams**, each with a human-readable **team name**.
- REQ-BAL-TEAM: Each team defines:
- A list of **ship entries**, each specifying: ship schematic (type), level, count, and an optional `modules` array defining the module layout applied to every ship of that entry. The `modules` array format is identical to that used in `ship_layouts.toml` (see Layout Blueprint TOML Format). If `modules` is omitted, ships of that entry have no modules. Invalid module instances (unknown type, position outside the grid, position on a non-buildable cell, or overlapping another module in the same entry) are silently skipped during loading.
- An optional list of **defence station entries**, each specifying: station type (`player_station` or `enemy_station` from `stations.toml`), level, and tile position (x, y).
- REQ-BAL-HQ: Each team has an HQ placed automatically at the vertical center of the arena at the far end of that team's buffer zone. HQ stats are read from `stations.toml [hq]` at level 1. Team 1's HQ is at the left edge; team 2's HQ is at the right edge.
- REQ-BAL-SPAWN: Team 1's ships spawn in team 1's buffer zone (left side); team 2's ships spawn in team 2's buffer zone (right side). Spawn positions are uniformly random within the respective buffer zone.
### Simulation
- REQ-BAL-SIM-ENV: Each arena simulates a pure-space environment using the same tick-based simulation as the main game. There is no asteroid, no buildings, no belts, no wave system, and no threat accumulation. Only ships, HQs, defence stations, and combat are active.
- REQ-BAL-SIM-AI: Ships use the same AI and stats as in the main game. All ships use aggressive stance and closest-target priority. Ships with no target in sensor range advance toward the enemy team's HQ. Ships that detect an enemy in sensor range engage it as in the normal game (REQ-SHP-COMBAT, REQ-SHP-ENEMY-AI).
- REQ-BAL-SIM-SPEED: Each arena that is not being inspected runs its simulation at maximum tick rate (as many ticks per second as the hardware allows), with no rendering. An inspected arena runs at a player-controllable game speed (same speed steps as the main game: 0×, 0.5×, 1×, 2×, 4×) with full rendering in the inspect window, defaulting to 1× on open.
- REQ-BAL-SIM-PARALLEL: All arenas are simulated in parallel, each on its own thread.
- REQ-BAL-SIM-END: An arena fight ends when either team's HQ is destroyed or all ships and defence stations of one team have been destroyed. If a team has no defence stations, destroying all its ships is sufficient. When the fight ends, the simulation for that arena stops.
### UI
- REQ-BAL-UI-WINDOW: On startup the tool displays a window containing a "Reload Config" button and a "Start All" button at the top (in that order, left to right), followed by a scrollable vertical list of arena widgets, one per arena defined in `balancing.toml`. Simulations do not start automatically on startup. All buttons and controls in the main window are disabled while an arena is being inspected (REQ-BAL-UI-INSPECT).
- REQ-BAL-UI-RELOAD: The "Reload Config" button reloads all config files from disk (`balancing.toml`, `ships.toml`, `stations.toml`), stops any running simulations, and replaces the arena widget list with freshly created widgets from the reloaded config. The button is disabled while any arena simulation is currently running.
- REQ-BAL-UI-START-ALL: The "Start All" button is placed above the scrollable arena list, to the right of the "Reload Config" button. Clicking it starts (or restarts) the simulation for every arena that is not currently running. The button is disabled when all arenas are currently running.
- REQ-BAL-UI-WIDGET: Each arena widget displays the arena name, an "Inspect" button (to the right of the arena name), and two columns (one per team). Each column shows the team name as a header, followed by a list of entries. The HQ is always the first entry in each column. Below the HQ, ship types are listed, followed by defence stations (if any). Each entry uses the format `surviving/total TypeName Llevel` — for example `2/3 Fighter L5` or `1/1 HQ L1`. The surviving count updates live as the simulation progresses. When the fight ends, the winning team's name header is prefixed with `[WON]`.
- REQ-BAL-UI-WIDGET-START: Each arena widget contains a "Start" button that starts the simulation for that arena. The button is disabled while the arena's simulation is running. When a finished arena's Start button is clicked, a fresh simulation is created and started (the widget resets to initial unit counts, the border returns to blue, and the previous results are replaced).
- REQ-BAL-UI-WIDGET-BORDER: Each arena widget has a colored border indicating its state: grey when not yet started, blue while its simulation is running, and green when the fight has ended.
- REQ-BAL-UI-INSPECT: Clicking an arena widget's "Inspect" button opens a new inspect window for that arena. Any previously open inspect window is closed first (its arena's simulation is aborted and its widget border returns to grey). The inspected arena is restarted with a fresh simulation that runs at controllable game speed with full rendering (REQ-BAL-SIM-SPEED). The arena widget updates live during inspection (surviving counts, border color, `[WON]` prefix) as it does for non-inspected arenas. Only one inspect window may be open at a time.
- REQ-BAL-UI-INSPECT-WINDOW: The inspect window consists of three sections, top to bottom: a title bar area containing the arena name and game speed controls (same buttons as the main game: 0×, 0.5×, 1×, 2×, 4×, with Space to toggle pause — see REQ-UI-SPEED and REQ-UI-HOTKEYS), the arena view in the center, and an info panel at the bottom displaying the same team columns and entry format as the arena widget in the main window (REQ-BAL-UI-WIDGET), updated live.
- REQ-BAL-UI-INSPECT-VIEW: The arena view renders all tiles of the arena and displays ships, HQs, defence stations, and laser beams using the same visual elements and `visuals.toml` colors as the main game. Team 1 uses player visual styles; team 2 uses enemy visual styles. The view has a fixed zoom level — no zoom or scroll is possible. The tile size is derived so that the full arena (all tiles) fits within the view.
- REQ-BAL-UI-INSPECT-CLOSE: Closing the inspect window (via the window's close button) aborts the inspected arena's simulation. The arena widget's border returns to grey and its surviving counts are left as they were at the moment of closing. All main window buttons and controls are re-enabled.

View File

@@ -4,6 +4,7 @@ set(TARGET_APP_NAME "${TARGET_BASE_NAME}")
set(TARGET_LIB_NAME "${TARGET_BASE_NAME}_lib")
set(TARGET_UI_NAME "${TARGET_BASE_NAME}_ui")
set(TARGET_TEST_NAME "${TARGET_BASE_NAME}_test")
set(TARGET_BALANCING_NAME "${TARGET_BASE_NAME}_balancing")
set(TARGET_LIB_INCLUDE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}/lib"
@@ -11,6 +12,7 @@ set(TARGET_LIB_INCLUDE_DIRS
)
set(TARGET_UI_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/ui")
set(TARGET_TEST_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/test")
set(TARGET_BALANCING_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/balancing")
set(SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
@@ -188,6 +190,74 @@ unset(HDRS)
unset(SRCS)
# ============================================================
# balancing — ship balancing tool; depends on lib + QtWidgets
# ============================================================
if (WIN32)
COPY_QT_BINARIES("${OUTPUT_ROOT_PATH}/Debug/balancing/" True)
COPY_QT_BINARIES("${OUTPUT_ROOT_PATH}/Release/balancing/" False)
execute_process(
COMMAND "cmd.exe" "/k" "rmdir" "${BACKSLASHED_OUTPUT_ROOT_PATH}\\Debug\\balancing\\data" & "mklink" "/d" "/j" "${BACKSLASHED_OUTPUT_ROOT_PATH}\\Debug\\balancing\\data" "${BACKSLASHED_CMAKE_SOURCE_DIR}\\bin\\balancing\\data" & exit
COMMAND "cmd.exe" "/k" "rmdir" "${BACKSLASHED_OUTPUT_ROOT_PATH}\\Release\\balancing\\data" & "mklink" "/d" "/j" "${BACKSLASHED_OUTPUT_ROOT_PATH}\\Release\\balancing\\data" "${BACKSLASHED_CMAKE_SOURCE_DIR}\\bin\\balancing\\data" & exit
)
endif ()
set(HDRS)
set(SRCS)
add_subdirectory(balancing)
set(RELATIVE_HDRS)
foreach (_file ${HDRS})
file(RELATIVE_PATH _relPath "${SRC_DIR}" "${_file}")
list(APPEND RELATIVE_HDRS "${_relPath}")
endforeach()
set(RELATIVE_SRCS)
foreach (_file ${SRCS})
file(RELATIVE_PATH _relPath "${SRC_DIR}" "${_file}")
list(APPEND RELATIVE_SRCS "${_relPath}")
endforeach()
add_files(BALANCING_FILES ${RELATIVE_HDRS} ${RELATIVE_SRCS})
add_executable(${TARGET_BALANCING_NAME} ${BALANCING_FILES})
create_source_groups(${BALANCING_FILES})
foreach (OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES})
string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)
set_target_properties(${TARGET_BALANCING_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${OUTPUT_ROOT_PATH}/${OUTPUTCONFIG}/balancing/"
)
endforeach ()
set_target_properties(${TARGET_BALANCING_NAME} PROPERTIES
AUTOMOC ON
CXX_STANDARD 17
VS_DEBUGGER_WORKING_DIRECTORY "${OUTPUT_ROOT_PATH}/$(Configuration)/balancing/"
)
target_include_directories(${TARGET_BALANCING_NAME} PRIVATE
"${TARGET_BALANCING_INCLUDE_DIRS}"
"${TARGET_LIB_INCLUDE_DIRS}"
"${TARGET_UI_INCLUDE_DIRS}"
"${LIB_INCLUDE_PATH}"
)
target_compile_definitions(${TARGET_BALANCING_NAME} PRIVATE
CONFIG_DIR="${CMAKE_SOURCE_DIR}/bin/app/data/config"
BALANCING_CONFIG="${CMAKE_SOURCE_DIR}/bin/balancing/data/balancing.toml"
TOML_FLOAT_CHARCONV=0
)
target_link_libraries(${TARGET_BALANCING_NAME} ${TARGET_LIB_NAME} Qt5::Widgets ${OPENGL_LIBRARIES})
unset(BALANCING_FILES)
unset(RELATIVE_HDRS)
unset(RELATIVE_SRCS)
unset(HDRS)
unset(SRCS)
# ============================================================
# tests — Catch2 tests; links against lib only (no QtWidgets)
# ============================================================
@@ -218,3 +288,4 @@ target_link_libraries(${TARGET_TEST_NAME} ${TARGET_LIB_NAME})
set(TARGET_LIB_NAME "${TARGET_LIB_NAME}" PARENT_SCOPE)
set(TARGET_UI_NAME "${TARGET_UI_NAME}" PARENT_SCOPE)
set(TARGET_APP_NAME "${TARGET_APP_NAME}" PARENT_SCOPE)
set(TARGET_BALANCING_NAME "${TARGET_BALANCING_NAME}" PARENT_SCOPE)

View File

@@ -0,0 +1,529 @@
#include "ArenaSimulation.h"
#include <algorithm>
#include <cassert>
#include <QVector2D>
#include "AiSystem.h"
#include "Building.h"
#include "BuildingSystem.h"
#include "BuildingType.h"
#include "CombatSystem.h"
#include "DynamicBodySystem.h"
#include "EntityAdmin.h"
#include "FactionComponent.h"
#include "HealthComponent.h"
#include "ModuleOwnerComponent.h"
#include "MovementIntentSystem.h"
#include "PositionComponent.h"
#include "ScrapSystem.h"
#include "ShipIdentityComponent.h"
#include "ShipSystem.h"
#include "ShipsConfig.h"
#include "StationBodyComponent.h"
#include "StationsConfig.h"
#include "SurfaceMask.h"
#include "WeaponComponent.h"
ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
ArenaConfig arenaConfig,
unsigned int seed)
: m_gameConfig(gameConfig)
, m_arenaConfig(std::move(arenaConfig))
, m_rng(seed)
, m_currentTick(0)
, m_nextBuildingId(1)
, m_beltSystem(1.0)
, m_team1HqEntity(entt::null)
, m_team2HqEntity(entt::null)
, m_finished(false)
, m_winnerTeam(-1)
, m_stopRequested(false)
{
m_buildingSystem = std::make_unique<BuildingSystem>(
m_gameConfig,
m_beltSystem,
[this]() { return allocateBuildingId(); },
[](int) {},
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
[](const std::string&) -> bool { return true; },
m_rng);
m_shipSystem = std::make_unique<ShipSystem>(m_gameConfig, m_admin);
m_aiSystem = std::make_unique<AiSystem>();
m_movementIntentSystem = std::make_unique<MovementIntentSystem>();
m_dynamicBodySystem = std::make_unique<DynamicBodySystem>();
m_combatSystem = std::make_unique<CombatSystem>(m_gameConfig);
m_scrapSystem = std::make_unique<ScrapSystem>(m_admin);
placeStructures();
spawnShips();
m_shipSystem->triggerRallyDeparture();
updateStatus();
}
ArenaSimulation::~ArenaSimulation() = default;
BuildingId ArenaSimulation::allocateBuildingId()
{
return m_nextBuildingId++;
}
void ArenaSimulation::placeStructures()
{
const int totalWidth = m_arenaConfig.playerBufferWidth_tiles
+ m_arenaConfig.contestZoneWidth_tiles
+ m_arenaConfig.enemyBufferWidth_tiles;
const int midY = m_arenaConfig.heightTiles / 2;
// Team 1 HQ — ECS proxy entity, player faction (isEnemy=false).
{
const ParsedSurfaceMask hqParsed =
parseSurfaceMask(m_gameConfig.stations.hq.surfaceMask, Rotation::East);
const int anchorX = 0;
const int anchorY = midY - hqParsed.footprint.height() / 2;
const float hp = static_cast<float>(
m_gameConfig.stations.hq.hpFormula.evaluate(1.0));
const QPoint anchor(anchorX, anchorY);
std::vector<QPoint> absCells;
for (const QPoint& rel : hqParsed.bodyCells)
{
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
}
m_team1HqEntity = m_admin.spawnStation(anchor, hqParsed.footprint, absCells,
hp, hp, false);
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
}
// Team 2 HQ — ECS proxy entity, enemy faction (isEnemy=true). No weapon.
{
const ParsedSurfaceMask hqParsed =
parseSurfaceMask(m_gameConfig.stations.hq.surfaceMask, Rotation::West);
const int anchorX = totalWidth - hqParsed.footprint.width();
const int anchorY = midY - hqParsed.footprint.height() / 2;
const float hp = static_cast<float>(
m_gameConfig.stations.hq.hpFormula.evaluate(1.0));
const QPoint anchor(anchorX, anchorY);
std::vector<QPoint> absCells;
for (const QPoint& rel : hqParsed.bodyCells)
{
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
}
m_team2HqEntity = m_admin.spawnStation(anchor, hqParsed.footprint, absCells,
hp, hp, true);
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
}
auto placeArenaStation = [&](const ArenaStationEntry& entry, bool isEnemy)
{
float hp = 0.0f;
WeaponComponent weapon;
weapon.cooldownTicks = 0.0f;
weapon.currentTarget = std::nullopt;
const double lv = static_cast<double>(entry.level);
const float tileSize = static_cast<float>(m_gameConfig.world.tileSize_m);
const std::vector<std::string>& mask = isEnemy
? m_gameConfig.stations.enemyStation.surfaceMask
: m_gameConfig.stations.playerStation.surfaceMask;
if (entry.stationType == "player_station")
{
hp = static_cast<float>(
m_gameConfig.stations.playerStation.hpFormula.evaluate(lv));
weapon.damage = static_cast<float>(
m_gameConfig.stations.playerStation.damageFormula.evaluate(lv));
weapon.range_tiles = static_cast<float>(
m_gameConfig.stations.playerStation.rangeFormula.evaluate(lv)) / tileSize;
weapon.fireRateHz = static_cast<float>(
m_gameConfig.stations.playerStation.fireRateFormula.evaluate(lv));
}
else
{
hp = static_cast<float>(
m_gameConfig.stations.enemyStation.hpFormula.evaluate(lv));
weapon.damage = static_cast<float>(
m_gameConfig.stations.enemyStation.damageFormula.evaluate(lv));
weapon.range_tiles = static_cast<float>(
m_gameConfig.stations.enemyStation.rangeFormula.evaluate(lv)) / tileSize;
weapon.fireRateHz = static_cast<float>(
m_gameConfig.stations.enemyStation.fireRateFormula.evaluate(lv));
}
const ParsedSurfaceMask parsed = parseSurfaceMask(mask, Rotation::East);
const QPoint& anchor = entry.position;
std::vector<QPoint> absCells;
for (const QPoint& rel : parsed.bodyCells)
{
absCells.push_back(QPoint(anchor.x() + rel.x(), anchor.y() + rel.y()));
}
const entt::entity stationEntity = m_admin.spawnStation(
anchor, parsed.footprint, absCells, hp, hp, isEnemy);
{
entt::entity wChild = m_admin.createModuleEntity();
m_admin.addComponent<WeaponComponent>(wChild, weapon);
m_admin.addComponent<ModuleOwnerComponent>(wChild,
ModuleOwnerComponent{stationEntity});
}
m_buildingSystem->registerTileOccupancy(absCells, allocateBuildingId());
};
for (const ArenaStationEntry& entry : m_arenaConfig.teams[0].stations)
{
placeArenaStation(entry, false);
}
for (const ArenaStationEntry& entry : m_arenaConfig.teams[1].stations)
{
placeArenaStation(entry, true);
}
}
void ArenaSimulation::spawnShips()
{
const int contestStart = m_arenaConfig.playerBufferWidth_tiles;
const int team2Start = contestStart + m_arenaConfig.contestZoneWidth_tiles;
const int totalWidth = team2Start + m_arenaConfig.enemyBufferWidth_tiles;
std::uniform_real_distribution<float> yDist(0.0f,
static_cast<float>(m_arenaConfig.heightTiles));
// Team 1: isEnemy=false, spawn in player buffer zone.
{
std::uniform_real_distribution<float> xDist(0.0f,
static_cast<float>(m_arenaConfig.playerBufferWidth_tiles));
for (const ArenaShipEntry& entry : m_arenaConfig.teams[0].ships)
{
for (int i = 0; i < entry.count; ++i)
{
const QVector2D pos(xDist(m_rng), yDist(m_rng));
m_shipSystem->spawn(entry.schematicId, entry.level, pos, false,
entry.layout);
}
}
}
// Team 2: isEnemy=true, spawn in enemy buffer zone.
{
std::uniform_real_distribution<float> xDist(
static_cast<float>(team2Start),
static_cast<float>(totalWidth));
for (const ArenaShipEntry& entry : m_arenaConfig.teams[1].ships)
{
for (int i = 0; i < entry.count; ++i)
{
const QVector2D pos(xDist(m_rng), yDist(m_rng));
m_shipSystem->spawn(entry.schematicId, entry.level, pos, true,
entry.layout);
}
}
}
}
void ArenaSimulation::run()
{
while (!m_finished && !m_stopRequested.load(std::memory_order_relaxed))
{
tick();
}
{
std::lock_guard<std::mutex> lock(m_statusMutex);
m_status.finished = true;
}
}
void ArenaSimulation::requestStop()
{
m_stopRequested.store(true, std::memory_order_relaxed);
}
ArenaStatus ArenaSimulation::status() const
{
std::lock_guard<std::mutex> lock(m_statusMutex);
return m_status;
}
void ArenaSimulation::tick()
{
// Ship behavior systems (tick step 7).
m_shipSystem->clearMovementIntents();
m_aiSystem->tickHomeReturnBehavior(m_admin);
m_aiSystem->tickThreatResponseBehavior(m_admin, *m_buildingSystem);
m_aiSystem->tickRepairBehavior(m_admin, *m_buildingSystem);
m_aiSystem->tickRepairTools(m_admin);
m_aiSystem->tickSalvageBehavior(m_admin, *m_scrapSystem, *m_buildingSystem);
// Combat resolution (tick step 8).
std::vector<WeaponFiredEvent> weaponFiredEvents;
m_combatSystem->tick(m_currentTick, m_admin, *m_buildingSystem, weaponFiredEvents);
m_weaponFiredEvents.insert(m_weaponFiredEvents.end(), weaponFiredEvents.begin(), weaponFiredEvents.end());
m_combatSystem->applyPendingDamage(m_currentTick, m_admin);
// Deaths (tick step 9, simplified).
tickDeaths();
// Movement (tick step 10).
m_movementIntentSystem->tick(m_admin);
m_dynamicBodySystem->tick(m_admin);
// Scrap despawn (tick step 11).
m_scrapSystem->tickDespawn(m_currentTick);
++m_currentTick;
if (m_currentTick % 30 == 0)
{
updateStatus();
}
}
void ArenaSimulation::tickDeaths()
{
// Dead ships.
std::vector<entt::entity> deadShips;
m_admin.forEach<ShipIdentityComponent, HealthComponent>(
[&deadShips](entt::entity e, const ShipIdentityComponent& /*si*/,
const HealthComponent& h)
{
if (h.hp <= 0.0f)
{
deadShips.push_back(e);
}
});
for (entt::entity deadEntity : deadShips)
{
const ShipIdentityComponent& si = m_admin.get<ShipIdentityComponent>(deadEntity);
const PositionComponent& pos = m_admin.get<PositionComponent>(deadEntity);
for (const ShipDef& def : m_gameConfig.ships.ships)
{
if (def.id == si.schematicId && def.loot.scrapDrop > 0)
{
const Tick despawnAt = m_currentTick
+ secondsToTicks(m_gameConfig.world.scrapDespawnSeconds);
m_scrapSystem->spawn(pos.value, def.loot.scrapDrop, despawnAt);
break;
}
}
m_shipSystem->despawn(deadEntity);
}
// Dead stations.
std::vector<entt::entity> deadStations;
m_admin.forEach<StationBodyComponent, HealthComponent>(
[&deadStations](entt::entity e, const StationBodyComponent& /*sb*/,
const HealthComponent& h)
{
if (h.hp <= 0.0f)
{
deadStations.push_back(e);
}
});
for (entt::entity deadEntity : deadStations)
{
const StationBodyComponent& sb = m_admin.get<StationBodyComponent>(deadEntity);
m_buildingSystem->unregisterTileOccupancy(sb.bodyCells);
{
std::vector<entt::entity> stationChildren;
m_admin.forEach<ModuleOwnerComponent>(
[&](entt::entity ce, const ModuleOwnerComponent& o)
{
if (o.owner == deadEntity) { stationChildren.push_back(ce); }
});
for (entt::entity ce : stationChildren) { m_admin.destroy(ce); }
}
m_admin.destroy(deadEntity);
}
// Check end conditions — HQ proxy entities.
const bool team1HqGone = !m_admin.isValid(m_team1HqEntity)
|| m_admin.get<HealthComponent>(m_team1HqEntity).hp <= 0.0f;
const bool team2HqGone = !m_admin.isValid(m_team2HqEntity)
|| m_admin.get<HealthComponent>(m_team2HqEntity).hp <= 0.0f;
if (team1HqGone || team2HqGone)
{
m_finished = true;
m_winnerTeam = team1HqGone ? 1 : 0;
updateStatus();
return;
}
// Check if all ships and defence stations of one team are destroyed.
bool team1HasUnits = false;
bool team2HasUnits = false;
m_admin.forEach<ShipIdentityComponent, FactionComponent>(
[&team1HasUnits, &team2HasUnits](entt::entity /*e*/,
const ShipIdentityComponent& /*si*/,
const FactionComponent& f)
{
if (f.isEnemy) { team2HasUnits = true; }
else { team1HasUnits = true; }
});
m_admin.forEach<StationBodyComponent, FactionComponent>(
[&team1HasUnits, &team2HasUnits](entt::entity /*e*/,
const StationBodyComponent& /*sb*/,
const FactionComponent& f)
{
if (f.isEnemy) { team2HasUnits = true; }
else { team1HasUnits = true; }
});
if (!team1HasUnits || !team2HasUnits)
{
m_finished = true;
m_winnerTeam = team1HasUnits ? 0 : 1;
updateStatus();
}
}
void ArenaSimulation::tickOnce()
{
if (!m_finished)
{
tick();
updateStatus();
}
}
std::vector<WeaponFiredEvent> ArenaSimulation::drainWeaponFiredEvents()
{
std::vector<WeaponFiredEvent> result;
result.swap(m_weaponFiredEvents);
return result;
}
bool ArenaSimulation::isFinished() const
{
return m_finished;
}
int ArenaSimulation::winnerTeam() const
{
return m_winnerTeam;
}
Tick ArenaSimulation::currentTick() const
{
return m_currentTick;
}
const ArenaConfig& ArenaSimulation::arenaConfig() const
{
return m_arenaConfig;
}
const BuildingSystem& ArenaSimulation::buildings() const
{
return *m_buildingSystem;
}
const ShipSystem& ArenaSimulation::ships() const
{
return *m_shipSystem;
}
const ScrapSystem& ArenaSimulation::scraps() const
{
return *m_scrapSystem;
}
EntityAdmin& ArenaSimulation::admin()
{
return m_admin;
}
const EntityAdmin& ArenaSimulation::admin() const
{
return m_admin;
}
void ArenaSimulation::updateStatus()
{
ArenaStatus newStatus;
newStatus.finished = m_finished;
newStatus.winnerTeam = m_winnerTeam;
for (int ti = 0; ti < 2; ++ti)
{
ArenaStatus::TeamStatus& teamStatus = newStatus.teams[ti];
teamStatus.name = m_arenaConfig.teams[ti].name;
// HQ entry (always first).
{
ArenaStatus::Entry hqEntry;
hqEntry.displayName = "HQ";
hqEntry.level = 1;
hqEntry.total = 1;
const entt::entity hqEntity = (ti == 0) ? m_team1HqEntity : m_team2HqEntity;
hqEntry.surviving = (m_admin.isValid(hqEntity)
&& m_admin.get<HealthComponent>(hqEntity).hp > 0.0f) ? 1 : 0;
teamStatus.entries.push_back(hqEntry);
}
// Ship entries.
for (const ArenaShipEntry& shipEntry : m_arenaConfig.teams[ti].ships)
{
ArenaStatus::Entry entry;
entry.displayName = shipEntry.schematicId;
entry.level = shipEntry.level;
entry.total = shipEntry.count;
int surviving = 0;
const bool isEnemyTeam = (ti == 1);
m_admin.forEach<ShipIdentityComponent, FactionComponent, HealthComponent>(
[&surviving, &shipEntry, isEnemyTeam](entt::entity /*e*/,
const ShipIdentityComponent& si, const FactionComponent& f,
const HealthComponent& h)
{
if (f.isEnemy == isEnemyTeam
&& si.schematicId == shipEntry.schematicId
&& si.level == shipEntry.level
&& h.hp > 0.0f)
{
++surviving;
}
});
entry.surviving = surviving;
teamStatus.entries.push_back(entry);
}
// Station entries.
for (std::size_t si = 0; si < m_arenaConfig.teams[ti].stations.size(); ++si)
{
const ArenaStationEntry& stationEntry = m_arenaConfig.teams[ti].stations[si];
ArenaStatus::Entry entry;
entry.displayName = "Station";
entry.level = stationEntry.level;
entry.total = 1;
int surviving = 0;
const bool isEnemyTeam = (ti == 1);
m_admin.forEach<StationBodyComponent, FactionComponent, HealthComponent>(
[&surviving, &stationEntry, isEnemyTeam](entt::entity /*e*/,
const StationBodyComponent& sb, const FactionComponent& f,
const HealthComponent& h)
{
if (f.isEnemy == isEnemyTeam
&& sb.anchor == stationEntry.position
&& h.hp > 0.0f)
{
surviving = 1;
}
});
entry.surviving = surviving;
teamStatus.entries.push_back(entry);
}
}
std::lock_guard<std::mutex> lock(m_statusMutex);
m_status = newStatus;
}

View File

@@ -0,0 +1,111 @@
#pragma once
#include <atomic>
#include <memory>
#include <mutex>
#include <random>
#include <string>
#include <vector>
#include "BalancingConfig.h"
#include "BeltSystem.h"
#include "EntityAdmin.h"
#include "BuildingId.h"
#include "entt/entity/entity.hpp"
#include "WeaponFiredEvent.h"
#include "GameConfig.h"
#include "Tick.h"
class AiSystem;
class BuildingSystem;
class CombatSystem;
class DynamicBodySystem;
class MovementIntentSystem;
class ShipSystem;
class ScrapSystem;
struct ArenaStatus
{
struct Entry
{
std::string displayName;
int level;
int total;
int surviving;
};
struct TeamStatus
{
std::string name;
std::vector<Entry> entries; // HQ first, then ships, then stations
};
TeamStatus teams[2];
bool finished = false;
int winnerTeam = -1; // 0 or 1 when finished; -1 while running
};
class ArenaSimulation
{
public:
ArenaSimulation(const GameConfig& gameConfig,
ArenaConfig arenaConfig,
unsigned int seed = 0);
~ArenaSimulation();
void run();
void requestStop();
void tickOnce();
std::vector<WeaponFiredEvent> drainWeaponFiredEvents();
ArenaStatus status() const;
bool isFinished() const;
int winnerTeam() const;
Tick currentTick() const;
const ArenaConfig& arenaConfig() const;
const BuildingSystem& buildings() const;
const ShipSystem& ships() const;
const ScrapSystem& scraps() const;
EntityAdmin& admin();
const EntityAdmin& admin() const;
private:
BuildingId allocateBuildingId();
void placeStructures();
void spawnShips();
void tick();
void tickDeaths();
void updateStatus();
const GameConfig& m_gameConfig;
ArenaConfig m_arenaConfig;
std::mt19937 m_rng;
Tick m_currentTick;
BuildingId m_nextBuildingId;
EntityAdmin m_admin;
BeltSystem m_beltSystem;
std::unique_ptr<BuildingSystem> m_buildingSystem;
std::unique_ptr<ShipSystem> m_shipSystem;
std::unique_ptr<AiSystem> m_aiSystem;
std::unique_ptr<MovementIntentSystem> m_movementIntentSystem;
std::unique_ptr<DynamicBodySystem> m_dynamicBodySystem;
std::unique_ptr<CombatSystem> m_combatSystem;
std::unique_ptr<ScrapSystem> m_scrapSystem;
entt::entity m_team1HqEntity;
entt::entity m_team2HqEntity;
bool m_finished;
int m_winnerTeam;
std::atomic<bool> m_stopRequested;
std::vector<WeaponFiredEvent> m_weaponFiredEvents;
mutable std::mutex m_statusMutex;
ArenaStatus m_status;
};

432
src/balancing/ArenaView.cpp Normal file
View File

@@ -0,0 +1,432 @@
#include "ArenaView.h"
#include <algorithm>
#include <cmath>
#include <optional>
#include <QMouseEvent>
#include <QPainter>
#include <QPoint>
#include "ArenaSimulation.h"
#include "Building.h"
#include "BuildingSystem.h"
#include "EntityHitTest.h"
#include "EntitySelectedEvent.h"
#include "EventManager.h"
#include "FacingComponent.h"
#include "FactionComponent.h"
#include "GameSpeedChangedEvent.h"
#include "HealthComponent.h"
#include "PositionComponent.h"
#include "ScrapSystem.h"
#include "ShipIdentityComponent.h"
#include "StationBodyComponent.h"
namespace
{
} // namespace
ArenaView::ArenaView(ArenaSimulation* sim, const VisualsConfig* visuals,
QWidget* parent)
: QOpenGLWidget(parent)
, m_sim(sim)
, m_visuals(visuals)
, m_wallMs(0)
, m_gameSpeedMultiplier(1.0)
, m_prevNonZeroSpeed(1.0)
, m_rng(std::random_device{}())
, m_finishedEmitted(false)
{
setFocusPolicy(Qt::StrongFocus);
m_renderTimer = new QTimer(this);
m_renderTimer->setInterval(16);
connect(m_renderTimer, &QTimer::timeout, this, &ArenaView::onFrame);
m_renderTimer->start();
m_frameTimer.start();
registerForEvent();
}
ArenaView::~ArenaView()
{
unregisterForEvent();
}
void ArenaView::setGameSpeed(double multiplier)
{
if (multiplier > 0.001)
{
m_prevNonZeroSpeed = multiplier;
}
m_gameSpeedMultiplier = multiplier;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<GameSpeedChangedEvent>(multiplier));
}
double ArenaView::gameSpeed() const
{
return m_gameSpeedMultiplier;
}
void ArenaView::stopRendering()
{
m_renderTimer->stop();
}
void ArenaView::togglePause()
{
if (m_gameSpeedMultiplier < 0.001)
{
setGameSpeed(m_prevNonZeroSpeed);
}
else
{
setGameSpeed(0.0);
}
}
void ArenaView::onFrame()
{
const qint64 elapsed = m_frameTimer.restart();
m_wallMs += elapsed;
{
const int ticks = m_tickDriver.advance(
static_cast<double>(elapsed), m_gameSpeedMultiplier);
for (int i = 0; i < ticks; ++i)
{
m_sim->tickOnce();
}
}
// Emit fire events via EventManager
{
const std::vector<WeaponFiredEvent> fires = m_sim->drainWeaponFiredEvents();
for (const WeaponFiredEvent& fe : fires)
{
EventManager::getInstance()->sendEventImmediately(
std::make_shared<WeaponFiredEvent>(fe));
}
}
// Expire old beams
{
std::vector<ActiveBeam> live;
for (const ActiveBeam& b : m_activeBeams)
{
if (m_wallMs - b.emittedWallMs < kBeamLifetimeMs)
{
live.push_back(b);
}
}
m_activeBeams = std::move(live);
}
if (m_sim->isFinished() && !m_finishedEmitted)
{
m_finishedEmitted = true;
}
update();
}
void ArenaView::handleEvent(std::shared_ptr<const WeaponFiredEvent> event)
{
float maxRadius = 0.125f;
if (m_sim->admin().isValid(event->target)
&& m_sim->admin().hasAll<StationBodyComponent>(event->target))
{
const StationBodyComponent& sb = m_sim->admin().get<StationBodyComponent>(event->target);
const int shorter = std::min(sb.footprint.width(),
sb.footprint.height());
maxRadius = shorter / 2.0f;
}
std::uniform_real_distribution<float> angleDist(0.0f, 6.28318530f);
std::uniform_real_distribution<float> radiusDist(0.0f, maxRadius);
const float angle = angleDist(m_rng);
const float radius = radiusDist(m_rng);
ActiveBeam beam;
beam.event = *event;
beam.emittedWallMs = m_wallMs;
beam.targetOffset = QVector2D(radius * std::cos(angle),
radius * std::sin(angle));
m_activeBeams.push_back(beam);
}
void ArenaView::paintGL()
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, false);
drawTiles(painter);
drawBuildings(painter);
drawStations(painter);
drawScrap(painter);
drawShips(painter);
drawBeams(painter);
}
// ---------------------------------------------------------------------------
// Coordinate helpers
// ---------------------------------------------------------------------------
float ArenaView::tilePx() const
{
const ArenaConfig& ac = m_sim->arenaConfig();
const int totalWidth = ac.playerBufferWidth_tiles
+ ac.contestZoneWidth_tiles
+ ac.enemyBufferWidth_tiles;
const int totalHeight = ac.heightTiles;
if (totalWidth <= 0 || totalHeight <= 0) { return 1.0f; }
const float pxPerTileH = static_cast<float>(height()) / static_cast<float>(totalHeight);
const float pxPerTileW = static_cast<float>(width()) / static_cast<float>(totalWidth);
return std::min(pxPerTileH, pxPerTileW);
}
QPointF ArenaView::worldToWidget(QVector2D worldPos) const
{
return QPointF(
static_cast<qreal>(worldPos.x() * tilePx()),
static_cast<qreal>(worldPos.y() * tilePx()));
}
QPointF ArenaView::tileToWidget(QPoint tile) const
{
return worldToWidget(QVector2D(static_cast<float>(tile.x()),
static_cast<float>(tile.y())));
}
QRectF ArenaView::tileRect(QPoint tile) const
{
const QPointF tl = tileToWidget(tile);
return QRectF(tl.x(), tl.y(),
static_cast<qreal>(tilePx()), static_cast<qreal>(tilePx()));
}
std::optional<QVector2D> ArenaView::entityPosition(entt::entity entity) const
{
if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll<PositionComponent>(entity))
{
return std::nullopt;
}
return m_sim->admin().get<PositionComponent>(entity).value;
}
QVector2D ArenaView::widgetToWorld(QPoint widgetPt) const
{
const float px = tilePx();
if (px < 0.001f) { return QVector2D(0.0f, 0.0f); }
return QVector2D(static_cast<float>(widgetPt.x()) / px,
static_cast<float>(widgetPt.y()) / px);
}
void ArenaView::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
const QVector2D worldPos = widgetToWorld(event->pos());
entt::entity hit = entityAtWorldPos(m_sim->admin(), worldPos);
if (hit != entt::null)
{
m_selectedEntity = hit;
}
else
{
m_selectedEntity = std::nullopt;
}
EventManager::getInstance()->sendEventImmediately(
std::make_shared<EntitySelectedEvent>(m_selectedEntity));
}
QOpenGLWidget::mousePressEvent(event);
}
// ---------------------------------------------------------------------------
// Rendering
// ---------------------------------------------------------------------------
void ArenaView::drawTiles(QPainter& painter)
{
const ArenaConfig& ac = m_sim->arenaConfig();
const int totalWidth = ac.playerBufferWidth_tiles
+ ac.contestZoneWidth_tiles
+ ac.enemyBufferWidth_tiles;
const int totalHeight = ac.heightTiles;
painter.setPen(Qt::NoPen);
for (int x = 0; x < totalWidth; ++x)
{
for (int y = 0; y < totalHeight; ++y)
{
painter.fillRect(tileRect(QPoint(x, y)), m_visuals->space.fill);
}
}
}
void ArenaView::drawBuildings(QPainter& painter)
{
for (const Building& b : m_sim->buildings().allBuildings())
{
const std::map<BuildingType, BuildingVisuals>::const_iterator it =
m_visuals->buildings.find(b.type);
if (it == m_visuals->buildings.end()) { continue; }
const BuildingVisuals& bv = it->second;
painter.setPen(Qt::NoPen);
for (const QPoint& cell : b.bodyCells)
{
painter.fillRect(tileRect(cell), bv.fill);
}
const QPointF tl = tileToWidget(b.anchor);
const QRectF bboxRect(tl.x(), tl.y(),
b.footprint.width() * static_cast<qreal>(tilePx()),
b.footprint.height() * static_cast<qreal>(tilePx()));
painter.setPen(QPen(bv.outline, 1));
painter.setBrush(Qt::NoBrush);
painter.drawRect(bboxRect);
if (!bv.glyph.isEmpty())
{
painter.setPen(bv.outline);
painter.drawText(bboxRect, Qt::AlignCenter, bv.glyph);
}
}
}
void ArenaView::drawScrap(QPainter& painter)
{
const float r = tilePx() * 0.2f;
for (const ScrapInfo& scrap : m_sim->scraps().allScrapInfo())
{
const QPointF center = worldToWidget(scrap.position);
painter.setBrush(QColor(128, 110, 90));
painter.setPen(QPen(QColor(50, 40, 30), 1));
painter.drawEllipse(center,
static_cast<qreal>(r), static_cast<qreal>(r));
}
}
void ArenaView::drawStations(QPainter& painter)
{
m_sim->admin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
[&](entt::entity e, const StationBodyComponent& sb, const FactionComponent& f, const HealthComponent& h)
{
const BuildingType visType = f.isEnemy
? BuildingType::EnemyDefenceStation
: BuildingType::PlayerDefenceStation;
const std::map<BuildingType, BuildingVisuals>::const_iterator it =
m_visuals->buildings.find(visType);
if (it == m_visuals->buildings.end()) { return; }
const BuildingVisuals& bv = it->second;
painter.setPen(Qt::NoPen);
for (const QPoint& cell : sb.bodyCells)
{
painter.fillRect(tileRect(cell), bv.fill);
}
const QPointF tl = tileToWidget(sb.anchor);
const QRectF bboxRect(tl.x(), tl.y(),
sb.footprint.width() * static_cast<qreal>(tilePx()),
sb.footprint.height() * static_cast<qreal>(tilePx()));
painter.setPen(QPen(bv.outline, 1));
painter.setBrush(Qt::NoBrush);
painter.drawRect(bboxRect);
if (h.maxHp > 0.0f)
{
const float fraction = std::max(0.0f, h.hp / h.maxHp);
const qreal barH = static_cast<qreal>(tilePx()) * 0.12;
const qreal barY = bboxRect.bottom() + 1.0;
const qreal barW = bboxRect.width();
painter.fillRect(QRectF(bboxRect.left(), barY, barW, barH),
QColor(60, 60, 60));
painter.fillRect(QRectF(bboxRect.left(), barY, barW * static_cast<qreal>(fraction), barH),
f.isEnemy ? QColor(200, 60, 60) : QColor(60, 200, 60));
}
if (m_selectedEntity.has_value() && *m_selectedEntity == e)
{
painter.setPen(QPen(QColor(255, 255, 0), 2));
painter.setBrush(Qt::NoBrush);
painter.drawRect(bboxRect.adjusted(-2, -2, 2, 2));
}
});
}
void ArenaView::drawShips(QPainter& painter)
{
m_sim->admin().forEach<ShipIdentityComponent, PositionComponent, FacingComponent,
FactionComponent, HealthComponent>(
[&](entt::entity e, const ShipIdentityComponent& si,
const PositionComponent& pos, const FacingComponent& facing,
const FactionComponent& fac, const HealthComponent& h)
{
const std::map<std::string, ShipVisuals>::const_iterator it =
m_visuals->ships.find(si.schematicId);
if (it == m_visuals->ships.end()) { return; }
const QPointF center = worldToWidget(pos.value);
const QVector2D dir(std::cos(facing.radians), std::sin(facing.radians));
const QVector2D perp(-dir.y(), dir.x());
const float fwd = tilePx() * 0.45f;
const float side = tilePx() * 0.25f;
QPolygonF tri;
tri << QPointF(center.x() + static_cast<qreal>(dir.x() * fwd),
center.y() + static_cast<qreal>(dir.y() * fwd))
<< QPointF(center.x() + static_cast<qreal>(perp.x() * side - dir.x() * side),
center.y() + static_cast<qreal>(perp.y() * side - dir.y() * side))
<< QPointF(center.x() + static_cast<qreal>(-perp.x() * side - dir.x() * side),
center.y() + static_cast<qreal>(-perp.y() * side - dir.y() * side));
painter.setPen(QPen(it->second.outline, 1));
painter.setBrush(it->second.fill);
painter.drawPolygon(tri);
if (h.maxHp > 0.0f)
{
const float fraction = std::max(0.0f, h.hp / h.maxHp);
const qreal barW = static_cast<qreal>(fwd) * 2.0;
const qreal barH = static_cast<qreal>(tilePx()) * 0.12;
const qreal barX = center.x() - static_cast<qreal>(fwd);
const qreal barY = center.y() + static_cast<qreal>(fwd) + 1.0;
painter.fillRect(QRectF(barX, barY, barW, barH), QColor(60, 60, 60));
painter.fillRect(QRectF(barX, barY, barW * static_cast<qreal>(fraction), barH),
fac.isEnemy ? QColor(200, 60, 60) : QColor(60, 200, 60));
}
if (m_selectedEntity.has_value() && *m_selectedEntity == e)
{
const qreal radius = static_cast<qreal>(tilePx()) * 0.55;
painter.setPen(QPen(QColor(255, 255, 0), 2));
painter.setBrush(Qt::NoBrush);
painter.drawEllipse(center, radius, radius);
}
});
}
void ArenaView::drawBeams(QPainter& painter)
{
painter.setPen(QPen(m_visuals->beams.color, m_visuals->beams.widthPx));
for (const ActiveBeam& beam : m_activeBeams)
{
const std::optional<QVector2D> shooterPos = entityPosition(beam.event.shooter);
const std::optional<QVector2D> targetPos = entityPosition(beam.event.target);
if (!shooterPos.has_value() || !targetPos.has_value()) { continue; }
painter.drawLine(worldToWidget(*shooterPos),
worldToWidget(*targetPos + beam.targetOffset));
}
}

89
src/balancing/ArenaView.h Normal file
View File

@@ -0,0 +1,89 @@
#pragma once
#include <optional>
#include <random>
#include <vector>
#include <QElapsedTimer>
#include <QOpenGLWidget>
#include <QTimer>
#include <QVector2D>
#include "EventHandler.h"
#include "WeaponFiredEvent.h"
#include "entt/entity/entity.hpp"
#include "EntitySelectedEvent.h"
#include "Tick.h"
#include "TickDriver.h"
#include "VisualsConfig.h"
class ArenaSimulation;
class QPainter;
class ArenaView : public QOpenGLWidget,
public EventHandler<WeaponFiredEvent>
{
Q_OBJECT
public:
ArenaView(ArenaSimulation* sim, const VisualsConfig* visuals,
QWidget* parent = nullptr);
~ArenaView() override;
void setGameSpeed(double multiplier);
double gameSpeed() const;
void togglePause();
void stopRendering();
protected:
void paintGL() override;
void mousePressEvent(QMouseEvent* event) override;
private slots:
void onFrame();
private:
void handleEvent(std::shared_ptr<const WeaponFiredEvent> event) override;
void drawTiles(QPainter& painter);
void drawBuildings(QPainter& painter);
void drawStations(QPainter& painter);
void drawScrap(QPainter& painter);
void drawShips(QPainter& painter);
void drawBeams(QPainter& painter);
float tilePx() const;
QPointF worldToWidget(QVector2D worldPos) const;
QPointF tileToWidget(QPoint tile) const;
QRectF tileRect(QPoint tile) const;
std::optional<QVector2D> entityPosition(entt::entity entity) const;
QVector2D widgetToWorld(QPoint widgetPt) const;
struct ActiveBeam
{
WeaponFiredEvent event;
qint64 emittedWallMs;
QVector2D targetOffset;
};
static constexpr qint64 kBeamLifetimeMs = 300;
ArenaSimulation* m_sim;
const VisualsConfig* m_visuals;
TickDriver m_tickDriver;
QElapsedTimer m_frameTimer;
qint64 m_wallMs;
std::mt19937 m_rng;
double m_gameSpeedMultiplier;
double m_prevNonZeroSpeed;
QTimer* m_renderTimer;
std::vector<ActiveBeam> m_activeBeams;
bool m_finishedEmitted;
std::optional<entt::entity> m_selectedEntity;
};

View File

@@ -0,0 +1,138 @@
#include "ArenaWidget.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include "ArenaInspectRequestedEvent.h"
#include "ArenaStartRequestedEvent.h"
#include "EventManager.h"
ArenaWidget::ArenaWidget(int arenaIndex, const std::string& arenaName, QWidget* parent)
: QFrame(parent)
, m_arenaIndex(arenaIndex)
, m_running(false)
, m_wasFinished(false)
{
buildLayout(arenaName);
setFrameStyle(QFrame::Box | QFrame::Plain);
setLineWidth(2);
setStyleSheet("ArenaWidget { border: 2px solid #999999; padding: 8px; }");
}
void ArenaWidget::buildLayout(const std::string& arenaName)
{
QVBoxLayout* outerLayout = new QVBoxLayout(this);
outerLayout->setContentsMargins(8, 8, 8, 8);
outerLayout->setSpacing(4);
QHBoxLayout* titleRow = new QHBoxLayout();
m_titleLabel = new QLabel(QString::fromStdString(arenaName), this);
QFont titleFont = m_titleLabel->font();
titleFont.setBold(true);
titleFont.setPointSize(titleFont.pointSize() + 2);
m_titleLabel->setFont(titleFont);
titleRow->addWidget(m_titleLabel);
titleRow->addStretch();
m_inspectButton = new QPushButton(tr("Inspect"), this);
connect(m_inspectButton, &QPushButton::clicked, this, [this]() {
EventManager::getInstance()->sendEventImmediately(
std::make_shared<ArenaInspectRequestedEvent>(m_arenaIndex));
});
titleRow->addWidget(m_inspectButton);
m_startButton = new QPushButton(tr("Start"), this);
connect(m_startButton, &QPushButton::clicked, this, [this]() {
EventManager::getInstance()->sendEventImmediately(
std::make_shared<ArenaStartRequestedEvent>(m_arenaIndex));
});
titleRow->addWidget(m_startButton);
outerLayout->addLayout(titleRow);
QHBoxLayout* teamsLayout = new QHBoxLayout();
teamsLayout->setSpacing(16);
// Team 1 column.
QVBoxLayout* team1Layout = new QVBoxLayout();
m_team1Header = new QLabel(this);
QFont headerFont = m_team1Header->font();
headerFont.setBold(true);
m_team1Header->setFont(headerFont);
team1Layout->addWidget(m_team1Header);
m_team1Content = new QLabel(this);
team1Layout->addWidget(m_team1Content);
team1Layout->addStretch();
teamsLayout->addLayout(team1Layout);
// Team 2 column.
QVBoxLayout* team2Layout = new QVBoxLayout();
m_team2Header = new QLabel(this);
m_team2Header->setFont(headerFont);
team2Layout->addWidget(m_team2Header);
m_team2Content = new QLabel(this);
team2Layout->addWidget(m_team2Content);
team2Layout->addStretch();
teamsLayout->addLayout(team2Layout);
outerLayout->addLayout(teamsLayout);
}
void ArenaWidget::startSimulation()
{
m_running = true;
m_wasFinished = false;
m_startButton->setEnabled(false);
setStyleSheet("ArenaWidget { border: 2px solid #3366ff; padding: 8px; }");
}
void ArenaWidget::resetToGrey()
{
m_running = false;
m_wasFinished = false;
m_startButton->setEnabled(true);
setStyleSheet("ArenaWidget { border: 2px solid #999999; padding: 8px; }");
}
void ArenaWidget::updateStatus(const ArenaStatus& status)
{
for (int ti = 0; ti < 2; ++ti)
{
const ArenaStatus::TeamStatus& team = status.teams[ti];
QLabel* header = (ti == 0) ? m_team1Header : m_team2Header;
QLabel* content = (ti == 0) ? m_team1Content : m_team2Content;
if (status.finished && status.winnerTeam == ti)
{
header->setText(tr("[WON] %1").arg(QString::fromStdString(team.name)));
}
else
{
header->setText(QString::fromStdString(team.name));
}
QString lines;
for (const ArenaStatus::Entry& entry : team.entries)
{
if (!lines.isEmpty())
{
lines += "\n";
}
lines += QString("%1/%2 %3 L%4")
.arg(entry.surviving)
.arg(entry.total)
.arg(QString::fromStdString(entry.displayName))
.arg(entry.level);
}
content->setText(lines);
}
if (status.finished && !m_wasFinished)
{
m_wasFinished = true;
m_running = false;
m_startButton->setEnabled(true);
setStyleSheet("ArenaWidget { border: 2px solid #33cc33; padding: 8px; }");
}
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include <string>
#include <vector>
#include <QFrame>
#include <QLabel>
#include <QPushButton>
#include "ArenaSimulation.h"
class ArenaWidget : public QFrame
{
Q_OBJECT
public:
ArenaWidget(int arenaIndex, const std::string& arenaName, QWidget* parent = nullptr);
void updateStatus(const ArenaStatus& status);
void startSimulation();
void resetToGrey();
private:
void buildLayout(const std::string& arenaName);
int m_arenaIndex;
QLabel* m_titleLabel;
QLabel* m_team1Header;
QLabel* m_team2Header;
QLabel* m_team1Content;
QLabel* m_team2Content;
QPushButton* m_inspectButton;
QPushButton* m_startButton;
bool m_running;
bool m_wasFinished;
};

View File

@@ -0,0 +1,206 @@
#include "BalancingConfig.h"
#include <stdexcept>
#include <string>
#include "toml.hpp"
#include "Rotation.h"
namespace
{
std::runtime_error makeError(const std::string& path, const std::string& why)
{
return std::runtime_error("balancing.toml: '" + path + "' " + why);
}
template <typename NodeView>
int64_t requireInt(NodeView node, const std::string& path)
{
const std::optional<int64_t> value = node.template value<int64_t>();
if (!value)
{
throw makeError(path, "missing or not an integer");
}
return *value;
}
template <typename NodeView>
std::string requireString(NodeView node, const std::string& path)
{
const std::optional<std::string> value = node.template value<std::string>();
if (!value)
{
throw makeError(path, "missing or not a string");
}
return *value;
}
Rotation parseRotation(const std::string& s)
{
if (s == "east") { return Rotation::East; }
if (s == "south") { return Rotation::South; }
if (s == "west") { return Rotation::West; }
return Rotation::North;
}
} // namespace
BalancingConfig loadBalancingConfig(const std::string& path)
{
toml::table root;
try
{
root = toml::parse_file(path);
}
catch (const toml::parse_error& e)
{
throw std::runtime_error(std::string("balancing.toml: parse error: ") + e.what());
}
const toml::array* arenaArray = root["arena"].as_array();
if (!arenaArray)
{
throw makeError("arena", "missing or not an array of tables");
}
BalancingConfig config;
for (std::size_t ai = 0; ai < arenaArray->size(); ++ai)
{
const toml::table* arenaTbl = (*arenaArray)[ai].as_table();
if (!arenaTbl)
{
throw makeError("arena[" + std::to_string(ai) + "]", "not a table");
}
const std::string prefix = "arena[" + std::to_string(ai) + "]";
ArenaConfig arena;
arena.name = requireString((*arenaTbl)["name"], prefix + ".name");
arena.heightTiles = static_cast<int>(
requireInt((*arenaTbl)["height_tiles"], prefix + ".height_tiles"));
arena.playerBufferWidth_tiles = static_cast<int>(
requireInt((*arenaTbl)["player_buffer_width_tiles"], prefix + ".player_buffer_width_tiles"));
arena.contestZoneWidth_tiles = static_cast<int>(
requireInt((*arenaTbl)["contest_zone_width_tiles"], prefix + ".contest_zone_width_tiles"));
arena.enemyBufferWidth_tiles = static_cast<int>(
requireInt((*arenaTbl)["enemy_buffer_width_tiles"], prefix + ".enemy_buffer_width_tiles"));
const toml::array* teamArray = (*arenaTbl)["team"].as_array();
if (!teamArray || teamArray->size() != 2)
{
throw makeError(prefix + ".team", "must contain exactly 2 teams");
}
for (int ti = 0; ti < 2; ++ti)
{
const toml::table* teamTbl = (*teamArray)[static_cast<std::size_t>(ti)].as_table();
if (!teamTbl)
{
throw makeError(prefix + ".team[" + std::to_string(ti) + "]", "not a table");
}
const std::string tPrefix = prefix + ".team[" + std::to_string(ti) + "]";
ArenaTeamConfig& team = arena.teams[ti];
team.name = requireString((*teamTbl)["name"], tPrefix + ".name");
const toml::array* shipArray = (*teamTbl)["ship"].as_array();
if (shipArray)
{
for (std::size_t si = 0; si < shipArray->size(); ++si)
{
const toml::table* shipTbl = (*shipArray)[si].as_table();
if (!shipTbl)
{
throw makeError(tPrefix + ".ship[" + std::to_string(si) + "]",
"not a table");
}
const std::string sPrefix =
tPrefix + ".ship[" + std::to_string(si) + "]";
ArenaShipEntry entry;
entry.schematicId = requireString((*shipTbl)["schematic"],
sPrefix + ".schematic");
entry.level = static_cast<int>(
requireInt((*shipTbl)["level"], sPrefix + ".level"));
entry.count = static_cast<int>(
requireInt((*shipTbl)["count"], sPrefix + ".count"));
const toml::array* modArray = (*shipTbl)["modules"].as_array();
if (modArray && !modArray->empty())
{
ShipLayoutConfig layout;
for (std::size_t mi = 0; mi < modArray->size(); ++mi)
{
const toml::table* modTbl = (*modArray)[mi].as_table();
if (!modTbl) { continue; }
const std::optional<std::string> type =
(*modTbl)["type"].value<std::string>();
const std::optional<int64_t> x =
(*modTbl)["x"].value<int64_t>();
const std::optional<int64_t> y =
(*modTbl)["y"].value<int64_t>();
const std::optional<std::string> rotStr =
(*modTbl)["rotation"].value<std::string>();
if (!type || !x || !y || !rotStr) { continue; }
PlacedModule pm;
pm.moduleId = *type;
pm.position = QPoint(static_cast<int>(*x),
static_cast<int>(*y));
pm.rotation = parseRotation(*rotStr);
layout.placedModules.push_back(std::move(pm));
}
entry.layout = std::move(layout);
}
team.ships.push_back(entry);
}
}
const toml::array* stationArray = (*teamTbl)["station"].as_array();
if (stationArray)
{
for (std::size_t si = 0; si < stationArray->size(); ++si)
{
const toml::table* stationTbl = (*stationArray)[si].as_table();
if (!stationTbl)
{
throw makeError(tPrefix + ".station[" + std::to_string(si) + "]",
"not a table");
}
const std::string sPrefix =
tPrefix + ".station[" + std::to_string(si) + "]";
ArenaStationEntry entry;
entry.stationType = requireString((*stationTbl)["type"],
sPrefix + ".type");
entry.level = static_cast<int>(
requireInt((*stationTbl)["level"], sPrefix + ".level"));
entry.position = QPoint(
static_cast<int>(requireInt((*stationTbl)["x"], sPrefix + ".x")),
static_cast<int>(requireInt((*stationTbl)["y"], sPrefix + ".y")));
if (entry.stationType != "player_station"
&& entry.stationType != "enemy_station")
{
throw makeError(sPrefix + ".type",
"must be 'player_station' or 'enemy_station'");
}
team.stations.push_back(entry);
}
}
}
config.arenas.push_back(arena);
}
return config;
}

View File

@@ -0,0 +1,48 @@
#pragma once
#include <optional>
#include <string>
#include <vector>
#include <QPoint>
#include "ShipLayout.h"
struct ArenaStationEntry
{
std::string stationType; // "player_station" or "enemy_station"
int level;
QPoint position;
};
struct ArenaShipEntry
{
std::string schematicId;
int level;
int count;
std::optional<ShipLayoutConfig> layout;
};
struct ArenaTeamConfig
{
std::string name;
std::vector<ArenaShipEntry> ships;
std::vector<ArenaStationEntry> stations;
};
struct ArenaConfig
{
std::string name;
int heightTiles;
int playerBufferWidth_tiles;
int contestZoneWidth_tiles;
int enemyBufferWidth_tiles;
ArenaTeamConfig teams[2];
};
struct BalancingConfig
{
std::vector<ArenaConfig> arenas;
};
BalancingConfig loadBalancingConfig(const std::string& path);

View File

@@ -0,0 +1,294 @@
#include "BalancingWindow.h"
#include <QHBoxLayout>
#include <QMessageBox>
#include <QVBoxLayout>
#include "ConfigLoader.h"
#include "InspectWindow.h"
#include "VisualsLoader.h"
BalancingWindow::BalancingWindow(const BalancingConfig& balancingConfig,
GameConfig gameConfig,
const std::string& configDir,
const std::string& balancingConfigPath,
QWidget* parent)
: QWidget(parent)
, m_gameConfig(std::move(gameConfig))
, m_configDir(configDir)
, m_balancingConfigPath(balancingConfigPath)
, m_nextSeed(0)
, m_inspectWindow(nullptr)
, m_inspectedArenaIndex(-1)
{
m_visuals = VisualsLoader::load(m_configDir + "/visuals.toml");
setWindowTitle(tr("DotaFactory — Balancing Tool"));
resize(800, 600);
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
QHBoxLayout* buttonRow = new QHBoxLayout();
m_reloadButton = new QPushButton(tr("Reload Config"), this);
m_startAllButton = new QPushButton(tr("Start All"), this);
buttonRow->addWidget(m_reloadButton);
buttonRow->addWidget(m_startAllButton);
buttonRow->addStretch();
mainLayout->addLayout(buttonRow);
connect(m_reloadButton, &QPushButton::clicked, this, &BalancingWindow::reloadConfig);
connect(m_startAllButton, &QPushButton::clicked, this, &BalancingWindow::startAll);
m_scrollArea = new QScrollArea(this);
m_scrollArea->setWidgetResizable(true);
mainLayout->addWidget(m_scrollArea);
populateArenas(balancingConfig);
m_pollTimer = new QTimer(this);
connect(m_pollTimer, &QTimer::timeout, this, &BalancingWindow::pollStatuses);
m_pollTimer->start(100);
registerForEvents();
}
BalancingWindow::~BalancingWindow()
{
unregisterForEvents();
m_pollTimer->stop();
if (m_inspectWindow)
{
delete m_inspectWindow;
m_inspectWindow = nullptr;
}
m_inspectedSim.reset();
stopAllArenas();
}
void BalancingWindow::populateArenas(const BalancingConfig& balancingConfig)
{
stopAllArenas();
m_arenas.clear();
QWidget* scrollContent = new QWidget(m_scrollArea);
QVBoxLayout* contentLayout = new QVBoxLayout(scrollContent);
contentLayout->setSpacing(8);
contentLayout->setContentsMargins(8, 8, 8, 8);
for (const ArenaConfig& arenaConfig : balancingConfig.arenas)
{
int index = static_cast<int>(m_arenas.size());
ArenaEntry entry;
entry.config = arenaConfig;
entry.simulation = std::make_unique<ArenaSimulation>(
m_gameConfig, arenaConfig, m_nextSeed++);
entry.widget = new ArenaWidget(index, arenaConfig.name, scrollContent);
contentLayout->addWidget(entry.widget);
entry.widget->updateStatus(entry.simulation->status());
m_arenas.push_back(std::move(entry));
}
contentLayout->addStretch();
m_scrollArea->setWidget(scrollContent);
updateButtons();
}
void BalancingWindow::stopAllArenas()
{
for (ArenaEntry& entry : m_arenas)
{
entry.simulation->requestStop();
}
for (ArenaEntry& entry : m_arenas)
{
if (entry.worker.joinable())
{
entry.worker.join();
}
}
}
void BalancingWindow::pollStatuses()
{
for (ArenaEntry& entry : m_arenas)
{
if (entry.worker.joinable())
{
const ArenaStatus status = entry.simulation->status();
entry.widget->updateStatus(status);
}
}
if (m_inspectedSim && m_inspectedArenaIndex >= 0)
{
const ArenaStatus status = m_inspectedSim->status();
m_arenas[static_cast<std::size_t>(m_inspectedArenaIndex)].widget->updateStatus(status);
}
updateButtons();
}
void BalancingWindow::reloadConfig()
{
try
{
GameConfig newGameConfig = ConfigLoader::loadFromDirectory(m_configDir);
BalancingConfig newBalancingConfig = loadBalancingConfig(m_balancingConfigPath);
m_gameConfig = std::move(newGameConfig);
populateArenas(newBalancingConfig);
}
catch (const std::exception& e)
{
QMessageBox::critical(this, tr("Reload Failed"), QString::fromStdString(e.what()));
}
}
void BalancingWindow::startAll()
{
for (int i = 0; i < static_cast<int>(m_arenas.size()); ++i)
{
startArena(i);
}
}
void BalancingWindow::handleEvent(std::shared_ptr<const ArenaStartRequestedEvent> event)
{
startArena(event->arenaIndex);
}
void BalancingWindow::handleEvent(std::shared_ptr<const ArenaInspectRequestedEvent> event)
{
inspectArena(event->arenaIndex);
}
void BalancingWindow::handleEvent(std::shared_ptr<const InspectWindowClosedEvent> /*event*/)
{
closeInspectWindow();
}
void BalancingWindow::startArena(int index)
{
ArenaEntry& entry = m_arenas[index];
if (entry.worker.joinable())
{
entry.simulation->requestStop();
entry.worker.join();
}
entry.simulation = std::make_unique<ArenaSimulation>(
m_gameConfig, entry.config, m_nextSeed++);
entry.widget->startSimulation();
entry.widget->updateStatus(entry.simulation->status());
ArenaSimulation* sim = entry.simulation.get();
entry.worker = std::thread([sim]() { sim->run(); });
updateButtons();
}
void BalancingWindow::inspectArena(int index)
{
if (m_inspectWindow)
{
delete m_inspectWindow;
m_inspectWindow = nullptr;
if (m_inspectedSim && m_inspectedArenaIndex >= 0
&& !m_inspectedSim->isFinished())
{
m_arenas[static_cast<std::size_t>(m_inspectedArenaIndex)].widget->resetToGrey();
}
m_inspectedSim.reset();
m_inspectedArenaIndex = -1;
}
ArenaEntry& entry = m_arenas[static_cast<std::size_t>(index)];
if (entry.worker.joinable())
{
entry.simulation->requestStop();
entry.worker.join();
}
m_inspectedSim = std::make_unique<ArenaSimulation>(
m_gameConfig, entry.config, m_nextSeed++);
m_inspectedArenaIndex = index;
entry.widget->resetToGrey();
entry.widget->startSimulation();
entry.widget->updateStatus(m_inspectedSim->status());
m_inspectWindow = new InspectWindow(
m_inspectedSim.get(), &m_gameConfig, &m_visuals, entry.config.name, nullptr);
setMainControlsEnabled(false);
m_inspectWindow->show();
}
void BalancingWindow::closeInspectWindow()
{
if (!m_inspectWindow)
{
return;
}
m_inspectWindow->deleteLater();
m_inspectWindow = nullptr;
if (m_inspectedArenaIndex >= 0 && m_inspectedSim)
{
if (!m_inspectedSim->isFinished())
{
m_arenas[static_cast<std::size_t>(m_inspectedArenaIndex)].widget->resetToGrey();
}
}
m_inspectedSim.reset();
m_inspectedArenaIndex = -1;
setMainControlsEnabled(true);
updateButtons();
}
void BalancingWindow::setMainControlsEnabled(bool enabled)
{
m_reloadButton->setEnabled(enabled);
m_startAllButton->setEnabled(enabled);
for (ArenaEntry& entry : m_arenas)
{
for (QPushButton* btn : entry.widget->findChildren<QPushButton*>())
{
btn->setEnabled(enabled);
}
}
}
void BalancingWindow::updateButtons()
{
if (m_inspectWindow)
{
return;
}
bool anyRunning = false;
bool allRunning = true;
for (ArenaEntry& entry : m_arenas)
{
if (entry.worker.joinable() && !entry.simulation->status().finished)
{
anyRunning = true;
}
else
{
allRunning = false;
}
}
if (m_arenas.empty())
{
allRunning = false;
}
m_reloadButton->setEnabled(!anyRunning);
m_startAllButton->setEnabled(!allRunning);
}

View File

@@ -0,0 +1,81 @@
#pragma once
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include <QPushButton>
#include <QScrollArea>
#include <QTimer>
#include <QWidget>
#include "ArenaInspectRequestedEvent.h"
#include "ArenaStartRequestedEvent.h"
#include "ArenaWidget.h"
#include "ArenaSimulation.h"
#include "BalancingConfig.h"
#include "EventHandler.h"
#include "GameConfig.h"
#include "InspectWindowClosedEvent.h"
#include "VisualsConfig.h"
class InspectWindow;
class BalancingWindow : public QWidget,
public CombinedEventHandler<ArenaStartRequestedEvent,
ArenaInspectRequestedEvent,
InspectWindowClosedEvent>
{
Q_OBJECT
public:
BalancingWindow(const BalancingConfig& balancingConfig,
GameConfig gameConfig,
const std::string& configDir,
const std::string& balancingConfigPath,
QWidget* parent = nullptr);
~BalancingWindow() override;
private:
void handleEvent(std::shared_ptr<const ArenaStartRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const ArenaInspectRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const InspectWindowClosedEvent> event) override;
private slots:
void pollStatuses();
void reloadConfig();
void startAll();
private:
void startArena(int index);
void inspectArena(int index);
void closeInspectWindow();
void populateArenas(const BalancingConfig& balancingConfig);
void stopAllArenas();
void updateButtons();
void setMainControlsEnabled(bool enabled);
struct ArenaEntry
{
ArenaConfig config;
std::unique_ptr<ArenaSimulation> simulation;
std::thread worker;
ArenaWidget* widget;
};
std::vector<ArenaEntry> m_arenas;
GameConfig m_gameConfig;
VisualsConfig m_visuals;
std::string m_configDir;
std::string m_balancingConfigPath;
unsigned int m_nextSeed;
QPushButton* m_reloadButton;
QPushButton* m_startAllButton;
QScrollArea* m_scrollArea;
QTimer* m_pollTimer;
InspectWindow* m_inspectWindow;
int m_inspectedArenaIndex;
std::unique_ptr<ArenaSimulation> m_inspectedSim;
};

View File

@@ -0,0 +1,27 @@
SET(HDRS
${HDRS}
${CMAKE_CURRENT_SOURCE_DIR}/ArenaWidget.h
${CMAKE_CURRENT_SOURCE_DIR}/ArenaSimulation.h
${CMAKE_CURRENT_SOURCE_DIR}/ArenaView.h
${CMAKE_CURRENT_SOURCE_DIR}/BalancingConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/BalancingWindow.h
${CMAKE_CURRENT_SOURCE_DIR}/InspectWindow.h
${CMAKE_CURRENT_SOURCE_DIR}/../ui/ShipStatsPanel.h
${CMAKE_CURRENT_SOURCE_DIR}/../ui/VisualsConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/../ui/VisualsLoader.h
PARENT_SCOPE
)
SET(SRCS
${SRCS}
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ArenaWidget.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ArenaSimulation.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ArenaView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/BalancingConfig.cpp
${CMAKE_CURRENT_SOURCE_DIR}/BalancingWindow.cpp
${CMAKE_CURRENT_SOURCE_DIR}/InspectWindow.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../ui/ShipStatsPanel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../ui/VisualsLoader.cpp
PARENT_SCOPE
)

View File

@@ -0,0 +1,364 @@
#include "InspectWindow.h"
#include <cmath>
#include <QCloseEvent>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QSignalMapper>
#include <QVBoxLayout>
#include "ArenaView.h"
#include "EntityAdmin.h"
#include "EventManager.h"
#include "HealthComponent.h"
#include "InspectWindowClosedEvent.h"
#include "ModuleOwnerComponent.h"
#include "ShipIdentityComponent.h"
#include "ShipStatsCalculator.h"
#include "ShipStatsPanel.h"
#include "StationBodyComponent.h"
#include "WeaponComponent.h"
const double InspectWindow::kSpeeds[] = { 0.0, 0.5, 1.0, 2.0, 10.0 };
const int InspectWindow::kSpeedCount = 5;
InspectWindow::InspectWindow(ArenaSimulation* sim, const GameConfig* config,
const VisualsConfig* visuals,
const std::string& arenaName, QWidget* parent)
: QWidget(parent)
, m_sim(sim)
, m_config(config)
{
setWindowTitle(tr("Inspect \u2014 %1").arg(QString::fromStdString(arenaName)));
resize(900, 700);
setAttribute(Qt::WA_DeleteOnClose, false);
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// Header: arena name + speed buttons
{
QWidget* header = new QWidget(this);
QHBoxLayout* headerLayout = new QHBoxLayout(header);
headerLayout->setContentsMargins(8, 4, 8, 4);
headerLayout->setSpacing(8);
QLabel* nameLabel = new QLabel(QString::fromStdString(arenaName), header);
QFont nameFont = nameLabel->font();
nameFont.setBold(true);
nameFont.setPointSize(nameFont.pointSize() + 2);
nameLabel->setFont(nameFont);
headerLayout->addWidget(nameLabel);
headerLayout->addStretch();
const char* labels[] = { "0x", "0.5x", "1x", "2x", "10x" };
QSignalMapper* mapper = new QSignalMapper(this);
for (int i = 0; i < kSpeedCount; ++i)
{
QPushButton* btn = new QPushButton(labels[i], header);
btn->setCheckable(true);
btn->setChecked(i == 2);
headerLayout->addWidget(btn);
m_speedButtons.push_back(btn);
mapper->setMapping(btn, i);
connect(btn, &QPushButton::clicked,
mapper, qOverload<>(&QSignalMapper::map));
}
connect(mapper, qOverload<int>(&QSignalMapper::mapped),
this, &InspectWindow::onSpeedButton);
header->setFixedHeight(header->sizeHint().height());
mainLayout->addWidget(header);
}
// Arena view (center, stretch)
m_arenaView = new ArenaView(sim, visuals, this);
mainLayout->addWidget(m_arenaView, 1);
// Info panel (bottom)
{
QWidget* infoPanel = new QWidget(this);
QHBoxLayout* infoLayout = new QHBoxLayout(infoPanel);
infoLayout->setContentsMargins(8, 4, 8, 4);
infoLayout->setSpacing(16);
QVBoxLayout* team1Layout = new QVBoxLayout();
m_team1Header = new QLabel(infoPanel);
QFont headerFont = m_team1Header->font();
headerFont.setBold(true);
m_team1Header->setFont(headerFont);
team1Layout->addWidget(m_team1Header);
m_team1Content = new QLabel(infoPanel);
team1Layout->addWidget(m_team1Content);
team1Layout->addStretch();
infoLayout->addLayout(team1Layout);
QVBoxLayout* team2Layout = new QVBoxLayout();
m_team2Header = new QLabel(infoPanel);
m_team2Header->setFont(headerFont);
team2Layout->addWidget(m_team2Header);
m_team2Content = new QLabel(infoPanel);
team2Layout->addWidget(m_team2Content);
team2Layout->addStretch();
infoLayout->addLayout(team2Layout);
// Entity stats section (right side of info panel)
QVBoxLayout* entityLayout = new QVBoxLayout();
m_entityTitleLabel = new QLabel(infoPanel);
QFont entityTitleFont = m_entityTitleLabel->font();
entityTitleFont.setBold(true);
m_entityTitleLabel->setFont(entityTitleFont);
m_entityTitleLabel->hide();
entityLayout->addWidget(m_entityTitleLabel);
m_entityStatsPanel = new ShipStatsPanel(config, infoPanel);
m_entityStatsPanel->hide();
entityLayout->addWidget(m_entityStatsPanel);
m_stationStatsLabel = new QLabel(infoPanel);
m_stationStatsLabel->setWordWrap(true);
m_stationStatsLabel->hide();
entityLayout->addWidget(m_stationStatsLabel);
entityLayout->addStretch();
infoLayout->addLayout(entityLayout);
mainLayout->addWidget(infoPanel);
}
// Poll timer for info panel updates
m_pollTimer = new QTimer(this);
connect(m_pollTimer, &QTimer::timeout, this, &InspectWindow::pollStatus);
m_pollTimer->start(100);
// Show initial status
pollStatus();
setFocusPolicy(Qt::StrongFocus);
registerForEvents();
}
InspectWindow::~InspectWindow()
{
unregisterForEvents();
}
void InspectWindow::closeEvent(QCloseEvent* event)
{
m_arenaView->stopRendering();
m_pollTimer->stop();
EventManager::getInstance()->sendEventImmediately(
std::make_shared<InspectWindowClosedEvent>());
event->accept();
}
void InspectWindow::keyPressEvent(QKeyEvent* event)
{
if (event->key() == Qt::Key_Space)
{
m_arenaView->togglePause();
}
else
{
QWidget::keyPressEvent(event);
}
}
void InspectWindow::onSpeedButton(int index)
{
if (index >= 0 && index < kSpeedCount)
{
m_arenaView->setGameSpeed(kSpeeds[index]);
}
}
void InspectWindow::handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event)
{
for (int i = 0; i < kSpeedCount; ++i)
{
const bool active = (std::abs(kSpeeds[i] - event->speed) < 0.001);
m_speedButtons[static_cast<std::size_t>(i)]->setChecked(active);
}
}
void InspectWindow::pollStatus()
{
const ArenaStatus status = m_sim->status();
updateInfoPanel(status);
refreshEntityStats();
}
void InspectWindow::updateInfoPanel(const ArenaStatus& status)
{
for (int ti = 0; ti < 2; ++ti)
{
const ArenaStatus::TeamStatus& team = status.teams[ti];
QLabel* header = (ti == 0) ? m_team1Header : m_team2Header;
QLabel* content = (ti == 0) ? m_team1Content : m_team2Content;
if (status.finished && status.winnerTeam == ti)
{
header->setText(tr("[WON] %1").arg(QString::fromStdString(team.name)));
}
else
{
header->setText(QString::fromStdString(team.name));
}
QString lines;
for (const ArenaStatus::Entry& entry : team.entries)
{
if (!lines.isEmpty())
{
lines += "\n";
}
lines += QString("%1/%2 %3 L%4")
.arg(entry.surviving)
.arg(entry.total)
.arg(QString::fromStdString(entry.displayName))
.arg(entry.level);
}
content->setText(lines);
}
}
void InspectWindow::handleEvent(std::shared_ptr<const EntitySelectedEvent> event)
{
if (event->entity.has_value())
{
m_selectedEntity = event->entity;
EntityAdmin& admin = m_sim->admin();
entt::entity entity = *m_selectedEntity;
if (!admin.isValid(entity))
{
m_selectedEntity = std::nullopt;
m_entityTitleLabel->hide();
m_entityStatsPanel->hide();
m_stationStatsLabel->hide();
return;
}
if (admin.hasAll<ShipIdentityComponent>(entity))
{
const ShipIdentityComponent& identity = admin.get<ShipIdentityComponent>(entity);
const HealthComponent& health = admin.get<HealthComponent>(entity);
m_entityTitleLabel->setText(tr("Ship: %1 (Lv %2)")
.arg(QString::fromStdString(identity.schematicId))
.arg(identity.level));
m_entityTitleLabel->show();
const ShipStats stats = buildShipStatsFromEntity(admin, entity);
m_entityStatsPanel->refreshFromLive(stats, health.hp);
m_entityStatsPanel->show();
m_stationStatsLabel->hide();
}
else if (admin.hasAll<StationBodyComponent>(entity))
{
const HealthComponent& health = admin.get<HealthComponent>(entity);
m_entityTitleLabel->setText(tr("Defence Station"));
m_entityTitleLabel->show();
float totalDps = 0.0f;
float maxRange = 0.0f;
bool hasWeapons = false;
admin.forEach<ModuleOwnerComponent, WeaponComponent>(
[&](entt::entity /*child*/, const ModuleOwnerComponent& owner, const WeaponComponent& w)
{
if (owner.owner != entity) { return; }
hasWeapons = true;
totalDps += w.damage * w.fireRateHz;
if (w.range_tiles > maxRange) { maxRange = w.range_tiles; }
});
QString statsText = tr("HP: %1 / %2")
.arg(static_cast<int>(health.hp + 0.5f))
.arg(static_cast<int>(health.maxHp + 0.5f));
if (hasWeapons)
{
statsText += tr("\nDPS: %1").arg(QString::number(static_cast<double>(totalDps), 'f', 1));
statsText += tr("\nRange: %1 tiles").arg(QString::number(static_cast<double>(maxRange), 'f', 1));
}
m_stationStatsLabel->setText(statsText);
m_stationStatsLabel->show();
m_entityStatsPanel->hide();
}
}
else
{
m_selectedEntity = std::nullopt;
m_entityTitleLabel->hide();
m_entityStatsPanel->hide();
m_stationStatsLabel->hide();
}
}
void InspectWindow::refreshEntityStats()
{
if (!m_selectedEntity.has_value()) { return; }
EntityAdmin& admin = m_sim->admin();
entt::entity entity = *m_selectedEntity;
if (!admin.isValid(entity))
{
m_selectedEntity = std::nullopt;
m_entityTitleLabel->hide();
m_entityStatsPanel->hide();
m_stationStatsLabel->hide();
return;
}
const HealthComponent& health = admin.get<HealthComponent>(entity);
if (health.hp <= 0.0f)
{
m_selectedEntity = std::nullopt;
m_entityTitleLabel->hide();
m_entityStatsPanel->hide();
m_stationStatsLabel->hide();
return;
}
if (admin.hasAll<ShipIdentityComponent>(entity))
{
const ShipStats stats = buildShipStatsFromEntity(admin, entity);
m_entityStatsPanel->refreshFromLive(stats, health.hp);
}
else if (admin.hasAll<StationBodyComponent>(entity))
{
float totalDps = 0.0f;
float maxRange = 0.0f;
bool hasWeapons = false;
admin.forEach<ModuleOwnerComponent, WeaponComponent>(
[&](entt::entity /*child*/, const ModuleOwnerComponent& owner, const WeaponComponent& w)
{
if (owner.owner != entity) { return; }
hasWeapons = true;
totalDps += w.damage * w.fireRateHz;
if (w.range_tiles > maxRange) { maxRange = w.range_tiles; }
});
QString statsText = tr("HP: %1 / %2")
.arg(static_cast<int>(health.hp + 0.5f))
.arg(static_cast<int>(health.maxHp + 0.5f));
if (hasWeapons)
{
statsText += tr("\nDPS: %1").arg(QString::number(static_cast<double>(totalDps), 'f', 1));
statsText += tr("\nRange: %1 tiles").arg(QString::number(static_cast<double>(maxRange), 'f', 1));
}
m_stationStatsLabel->setText(statsText);
}
}

View File

@@ -0,0 +1,70 @@
#pragma once
#include <optional>
#include <string>
#include <vector>
#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include <QWidget>
#include "entt/entity/entity.hpp"
#include "ArenaSimulation.h"
#include "EntitySelectedEvent.h"
#include "EventHandler.h"
#include "GameConfig.h"
#include "GameSpeedChangedEvent.h"
#include "VisualsConfig.h"
class ArenaView;
class ShipStatsPanel;
class InspectWindow : public QWidget,
public CombinedEventHandler<EntitySelectedEvent,
GameSpeedChangedEvent>
{
Q_OBJECT
public:
InspectWindow(ArenaSimulation* sim, const GameConfig* config,
const VisualsConfig* visuals,
const std::string& arenaName, QWidget* parent = nullptr);
~InspectWindow() override;
protected:
void closeEvent(QCloseEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
private:
void handleEvent(std::shared_ptr<const EntitySelectedEvent> event) override;
void handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event) override;
private slots:
void onSpeedButton(int index);
void pollStatus();
private:
void updateInfoPanel(const ArenaStatus& status);
void refreshEntityStats();
ArenaSimulation* m_sim;
const GameConfig* m_config;
ArenaView* m_arenaView;
std::vector<QPushButton*> m_speedButtons;
QLabel* m_team1Header;
QLabel* m_team2Header;
QLabel* m_team1Content;
QLabel* m_team2Content;
QTimer* m_pollTimer;
std::optional<entt::entity> m_selectedEntity;
QLabel* m_entityTitleLabel;
ShipStatsPanel* m_entityStatsPanel;
QLabel* m_stationStatsLabel;
static const double kSpeeds[];
static const int kSpeedCount;
};

34
src/balancing/main.cpp Normal file
View File

@@ -0,0 +1,34 @@
#include <memory>
#include <QApplication>
#include "BalancingConfig.h"
#include "BalancingWindow.h"
#include "ConfigLoader.h"
#include "ConsoleLogger.h"
#include "logging.h"
#include "LogManager.h"
int main(int argc, char* argv[])
{
LogManager::getInstance()->addLogger(std::make_shared<ConsoleLogger>());
LogManager::getInstance()->setLoggingEnabled(true);
LOG_INFO("Balancing tool starting");
QApplication::setApplicationName("DotaFactory Balancing");
if (QSysInfo::windowsVersion() != QSysInfo::WV_None)
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
}
QApplication application(argc, argv);
GameConfig gameConfig = ConfigLoader::loadFromDirectory(CONFIG_DIR);
BalancingConfig balancingConfig = loadBalancingConfig(BALANCING_CONFIG);
BalancingWindow window(balancingConfig, std::move(gameConfig), CONFIG_DIR, BALANCING_CONFIG);
window.show();
return application.exec();
}

View File

@@ -8,14 +8,10 @@ add_files(
toml++/toml.h
)
# Expose each external library's own directory on the include path so that
# source files can reference its headers without a subdirectory prefix:
# #include "catch.hpp" // instead of "catch/catch.hpp"
# #include "tinyexpr.h" // instead of "tinyexpr/tinyexpr.h"
# #include "toml.hpp" // instead of "toml++/toml.hpp"
set(LIB_INCLUDE_PATH
${LIB_INCLUDE_PATH}
${CMAKE_CURRENT_SOURCE_DIR}/catch
${CMAKE_CURRENT_SOURCE_DIR}/entt/src
${CMAKE_CURRENT_SOURCE_DIR}/tinyexpr
${CMAKE_CURRENT_SOURCE_DIR}/toml++
PARENT_SCOPE

11
src/external/entt/src/BUILD.bazel vendored Normal file
View File

@@ -0,0 +1,11 @@
load("@bazel_skylib//lib:selects.bzl", "selects")
load("//bazel:copts.bzl", "COPTS")
package(default_visibility = ["//:__subpackages__"])
cc_library(
name = "entt",
includes = ["."],
hdrs = glob(["**/*.h", "**/*.hpp"]),
copts = COPTS,
)

View File

@@ -0,0 +1,107 @@
#ifndef ENTT_CONFIG_CONFIG_H
#define ENTT_CONFIG_CONFIG_H
#include "version.h"
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION)
# define ENTT_CONSTEXPR
# define ENTT_THROW throw
# define ENTT_TRY try
# define ENTT_CATCH catch(...)
#else
# define ENTT_CONSTEXPR constexpr // use only with throwing functions (waiting for C++20)
# define ENTT_THROW
# define ENTT_TRY if(true)
# define ENTT_CATCH if(false)
#endif
#if __has_include(<version>)
# include <version>
#
# if defined(__cpp_consteval)
# define ENTT_CONSTEVAL consteval
# endif
#endif
#ifndef ENTT_CONSTEVAL
# define ENTT_CONSTEVAL constexpr
#endif
#ifdef ENTT_USE_ATOMIC
# include <atomic>
# define ENTT_MAYBE_ATOMIC(Type) std::atomic<Type>
#else
# define ENTT_MAYBE_ATOMIC(Type) Type
#endif
#ifndef ENTT_ID_TYPE
# include <cstdint>
# define ENTT_ID_TYPE std::uint32_t
#else
# include <cstdint> // provides coverage for types in the std namespace
#endif
#ifndef ENTT_SPARSE_PAGE
# define ENTT_SPARSE_PAGE 4096
#endif
#ifndef ENTT_PACKED_PAGE
# define ENTT_PACKED_PAGE 1024
#endif
#ifdef ENTT_DISABLE_ASSERT
# undef ENTT_ASSERT
# define ENTT_ASSERT(condition, msg) (void(0))
#elif !defined ENTT_ASSERT
# include <cassert>
# define ENTT_ASSERT(condition, msg) assert(((condition) && (msg)))
#endif
#ifdef ENTT_DISABLE_ASSERT
# undef ENTT_ASSERT_CONSTEXPR
# define ENTT_ASSERT_CONSTEXPR(condition, msg) (void(0))
#elif !defined ENTT_ASSERT_CONSTEXPR
# define ENTT_ASSERT_CONSTEXPR(condition, msg) ENTT_ASSERT(condition, msg)
#endif
#define ENTT_FAIL(msg) ENTT_ASSERT(false, msg);
#ifdef ENTT_NO_ETO
# define ENTT_ETO_TYPE(Type) void
#else
# define ENTT_ETO_TYPE(Type) Type
#endif
#ifdef ENTT_NO_MIXIN
# define ENTT_STORAGE(Mixin, ...) __VA_ARGS__
#else
# define ENTT_STORAGE(Mixin, ...) Mixin<__VA_ARGS__>
#endif
#ifdef ENTT_STANDARD_CPP
# define ENTT_NONSTD false
#else
# define ENTT_NONSTD true
# if defined __clang__ || defined __GNUC__
# define ENTT_PRETTY_FUNCTION __PRETTY_FUNCTION__
# define ENTT_PRETTY_FUNCTION_PREFIX '='
# define ENTT_PRETTY_FUNCTION_SUFFIX ']'
# elif defined _MSC_VER
# define ENTT_PRETTY_FUNCTION __FUNCSIG__
# define ENTT_PRETTY_FUNCTION_PREFIX '<'
# define ENTT_PRETTY_FUNCTION_SUFFIX '>'
# endif
#endif
#if defined _MSC_VER
# pragma detect_mismatch("entt.version", ENTT_VERSION)
# pragma detect_mismatch("entt.noexcept", ENTT_XSTR(ENTT_TRY))
# pragma detect_mismatch("entt.id", ENTT_XSTR(ENTT_ID_TYPE))
# pragma detect_mismatch("entt.nonstd", ENTT_XSTR(ENTT_NONSTD))
#endif
// NOLINTEND(cppcoreguidelines-macro-usage)
#endif

View File

@@ -0,0 +1,11 @@
#ifndef ENTT_CONFIG_MACRO_H
#define ENTT_CONFIG_MACRO_H
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#define ENTT_STR(arg) #arg
#define ENTT_XSTR(arg) ENTT_STR(arg)
// NOLINTEND(cppcoreguidelines-macro-usage)
#endif

View File

@@ -0,0 +1,18 @@
#ifndef ENTT_CONFIG_VERSION_H
#define ENTT_CONFIG_VERSION_H
#include "macro.h"
// NOLINTBEGIN(cppcoreguidelines-macro-*,modernize-macro-*)
#define ENTT_VERSION_MAJOR 3
#define ENTT_VERSION_MINOR 15
#define ENTT_VERSION_PATCH 0
#define ENTT_VERSION \
ENTT_XSTR(ENTT_VERSION_MAJOR) \
"." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH)
// NOLINTEND(cppcoreguidelines-macro-*,modernize-macro-*)
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,929 @@
#ifndef ENTT_CONTAINER_DENSE_SET_HPP
#define ENTT_CONTAINER_DENSE_SET_HPP
#include <cmath>
#include <cstddef>
#include <functional>
#include <iterator>
#include <limits>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../core/bit.hpp"
#include "../core/compressed_pair.hpp"
#include "../core/type_traits.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename It>
class dense_set_iterator final {
template<typename>
friend class dense_set_iterator;
public:
using value_type = typename It::value_type::second_type;
using pointer = const value_type *;
using reference = const value_type &;
using difference_type = std::ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
constexpr dense_set_iterator() noexcept
: it{} {}
constexpr dense_set_iterator(const It iter) noexcept
: it{iter} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
constexpr dense_set_iterator(const dense_set_iterator<Other> &other) noexcept
: it{other.it} {}
constexpr dense_set_iterator &operator++() noexcept {
return ++it, *this;
}
constexpr dense_set_iterator operator++(int) noexcept {
const dense_set_iterator orig = *this;
return ++(*this), orig;
}
constexpr dense_set_iterator &operator--() noexcept {
return --it, *this;
}
constexpr dense_set_iterator operator--(int) noexcept {
const dense_set_iterator orig = *this;
return operator--(), orig;
}
constexpr dense_set_iterator &operator+=(const difference_type value) noexcept {
it += value;
return *this;
}
constexpr dense_set_iterator operator+(const difference_type value) const noexcept {
dense_set_iterator copy = *this;
return (copy += value);
}
constexpr dense_set_iterator &operator-=(const difference_type value) noexcept {
return (*this += -value);
}
constexpr dense_set_iterator operator-(const difference_type value) const noexcept {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
return it[value].second;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return std::addressof(operator[](0));
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return operator[](0);
}
template<typename Lhs, typename Rhs>
friend constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
template<typename Lhs, typename Rhs>
friend constexpr bool operator==(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
template<typename Lhs, typename Rhs>
friend constexpr bool operator<(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
private:
It it;
};
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return lhs.it - rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return lhs.it == rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return !(lhs == rhs);
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return lhs.it < rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return rhs < lhs;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return !(lhs > rhs);
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return !(lhs < rhs);
}
template<typename It>
class dense_set_local_iterator final {
template<typename>
friend class dense_set_local_iterator;
public:
using value_type = typename It::value_type::second_type;
using pointer = const value_type *;
using reference = const value_type &;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
constexpr dense_set_local_iterator() noexcept
: it{},
offset{} {}
constexpr dense_set_local_iterator(It iter, const std::size_t pos) noexcept
: it{iter},
offset{pos} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
constexpr dense_set_local_iterator(const dense_set_local_iterator<Other> &other) noexcept
: it{other.it},
offset{other.offset} {}
constexpr dense_set_local_iterator &operator++() noexcept {
return offset = it[static_cast<typename It::difference_type>(offset)].first, *this;
}
constexpr dense_set_local_iterator operator++(int) noexcept {
const dense_set_local_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return std::addressof(it[static_cast<typename It::difference_type>(offset)].second);
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return *operator->();
}
[[nodiscard]] constexpr std::size_t index() const noexcept {
return offset;
}
private:
It it;
std::size_t offset;
};
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept {
return lhs.index() == rhs.index();
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept {
return !(lhs == rhs);
}
} // namespace internal
/*! @endcond */
/**
* @brief Associative container for unique objects of a given type.
*
* Internally, elements are organized into buckets. Which bucket an element is
* placed into depends entirely on its hash. Elements with the same hash code
* appear in the same bucket.
*
* @tparam Type Value type of the associative container.
* @tparam Hash Type of function to use to hash the values.
* @tparam KeyEqual Type of function to use to compare the values for equality.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Hash, typename KeyEqual, typename Allocator>
class dense_set {
static constexpr float default_threshold = 0.875f;
static constexpr std::size_t minimum_capacity = 8u;
using node_type = std::pair<std::size_t, Type>;
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
template<typename Other>
[[nodiscard]] std::size_t value_to_bucket(const Other &value) const noexcept {
return fast_mod(static_cast<size_type>(sparse.second()(value)), bucket_count());
}
template<typename Other>
[[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) {
for(auto it = begin(bucket), last = end(bucket); it != last; ++it) {
if(packed.second()(*it, value)) {
return begin() + static_cast<difference_type>(it.index());
}
}
return end();
}
template<typename Other>
[[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) const {
for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) {
if(packed.second()(*it, value)) {
return cbegin() + static_cast<difference_type>(it.index());
}
}
return cend();
}
template<typename Other>
[[nodiscard]] auto insert_or_do_nothing(Other &&value) {
const auto index = value_to_bucket(value);
if(auto it = constrained_find(value, index); it != end()) {
return std::make_pair(it, false);
}
packed.first().emplace_back(sparse.first()[index], std::forward<Other>(value));
sparse.first()[index] = packed.first().size() - 1u;
rehash_if_required();
return std::make_pair(--end(), true);
}
void move_and_pop(const std::size_t pos) {
if(const auto last = size() - 1u; pos != last) {
size_type *curr = &sparse.first()[value_to_bucket(packed.first().back().second)];
packed.first()[pos] = std::move(packed.first().back());
for(; *curr != last; curr = &packed.first()[*curr].first) {}
*curr = pos;
}
packed.first().pop_back();
}
void rehash_if_required() {
if(const auto bc = bucket_count(); size() > static_cast<size_type>(static_cast<float>(bc) * max_load_factor())) {
rehash(bc * 2u);
}
}
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Key type of the container. */
using key_type = Type;
/*! @brief Value type of the container. */
using value_type = Type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Signed integer type. */
using difference_type = std::ptrdiff_t;
/*! @brief Type of function to use to hash the elements. */
using hasher = Hash;
/*! @brief Type of function to use to compare the elements for equality. */
using key_equal = KeyEqual;
/*! @brief Random access iterator type. */
using iterator = internal::dense_set_iterator<typename packed_container_type::iterator>;
/*! @brief Constant random access iterator type. */
using const_iterator = internal::dense_set_iterator<typename packed_container_type::const_iterator>;
/*! @brief Reverse iterator type. */
using reverse_iterator = std::reverse_iterator<iterator>;
/*! @brief Constant reverse iterator type. */
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/*! @brief Forward iterator type. */
using local_iterator = internal::dense_set_local_iterator<typename packed_container_type::iterator>;
/*! @brief Constant forward iterator type. */
using const_local_iterator = internal::dense_set_local_iterator<typename packed_container_type::const_iterator>;
/*! @brief Default constructor. */
dense_set()
: dense_set{minimum_capacity} {}
/**
* @brief Constructs an empty container with a given allocator.
* @param allocator The allocator to use.
*/
explicit dense_set(const allocator_type &allocator)
: dense_set{minimum_capacity, hasher{}, key_equal{}, allocator} {}
/**
* @brief Constructs an empty container with a given allocator and user
* supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param allocator The allocator to use.
*/
dense_set(const size_type cnt, const allocator_type &allocator)
: dense_set{cnt, hasher{}, key_equal{}, allocator} {}
/**
* @brief Constructs an empty container with a given allocator, hash
* function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param hash Hash function to use.
* @param allocator The allocator to use.
*/
dense_set(const size_type cnt, const hasher &hash, const allocator_type &allocator)
: dense_set{cnt, hash, key_equal{}, allocator} {}
/**
* @brief Constructs an empty container with a given allocator, hash
* function, compare function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param hash Hash function to use.
* @param equal Compare function to use.
* @param allocator The allocator to use.
*/
explicit dense_set(const size_type cnt, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{})
: sparse{allocator, hash},
packed{allocator, equal} {
rehash(cnt);
}
/*! @brief Default copy constructor. */
dense_set(const dense_set &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
dense_set(const dense_set &other, const allocator_type &allocator)
: sparse{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())},
packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())},
threshold{other.threshold} {}
/*! @brief Default move constructor. */
dense_set(dense_set &&) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
dense_set(dense_set &&other, const allocator_type &allocator)
: sparse{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))},
packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))},
threshold{other.threshold} {}
/*! @brief Default destructor. */
~dense_set() = default;
/**
* @brief Default copy assignment operator.
* @return This container.
*/
dense_set &operator=(const dense_set &) = default;
/**
* @brief Default move assignment operator.
* @return This container.
*/
dense_set &operator=(dense_set &&) noexcept = default;
/**
* @brief Exchanges the contents with those of a given container.
* @param other Container to exchange the content with.
*/
void swap(dense_set &other) noexcept {
using std::swap;
swap(sparse, other.sparse);
swap(packed, other.packed);
swap(threshold, other.threshold);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return sparse.first().get_allocator();
}
/**
* @brief Returns an iterator to the beginning.
*
* If the array is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first instance of the internal array.
*/
[[nodiscard]] const_iterator cbegin() const noexcept {
return packed.first().begin();
}
/*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept {
return cbegin();
}
/*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept {
return packed.first().begin();
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last instance of the
* internal array.
*/
[[nodiscard]] const_iterator cend() const noexcept {
return packed.first().end();
}
/*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept {
return cend();
}
/*! @copydoc end */
[[nodiscard]] iterator end() noexcept {
return packed.first().end();
}
/**
* @brief Returns a reverse iterator to the beginning.
*
* If the array is empty, the returned iterator will be equal to `rend()`.
*
* @return An iterator to the first instance of the reversed internal array.
*/
[[nodiscard]] const_reverse_iterator crbegin() const noexcept {
return std::make_reverse_iterator(cend());
}
/*! @copydoc crbegin */
[[nodiscard]] const_reverse_iterator rbegin() const noexcept {
return crbegin();
}
/*! @copydoc rbegin */
[[nodiscard]] reverse_iterator rbegin() noexcept {
return std::make_reverse_iterator(end());
}
/**
* @brief Returns a reverse iterator to the end.
* @return An iterator to the element following the last instance of the
* reversed internal array.
*/
[[nodiscard]] const_reverse_iterator crend() const noexcept {
return std::make_reverse_iterator(cbegin());
}
/*! @copydoc crend */
[[nodiscard]] const_reverse_iterator rend() const noexcept {
return crend();
}
/*! @copydoc rend */
[[nodiscard]] reverse_iterator rend() noexcept {
return std::make_reverse_iterator(begin());
}
/**
* @brief Checks whether a container is empty.
* @return True if the container is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
return packed.first().empty();
}
/**
* @brief Returns the number of elements in a container.
* @return Number of elements in a container.
*/
[[nodiscard]] size_type size() const noexcept {
return packed.first().size();
}
/**
* @brief Returns the maximum possible number of elements.
* @return Maximum possible number of elements.
*/
[[nodiscard]] size_type max_size() const noexcept {
return packed.first().max_size();
}
/*! @brief Clears the container. */
void clear() noexcept {
sparse.first().clear();
packed.first().clear();
rehash(0u);
}
/**
* @brief Inserts an element into the container, if it does not exist.
* @param value An element to insert into the container.
* @return A pair consisting of an iterator to the inserted element (or to
* the element that prevented the insertion) and a bool denoting whether the
* insertion took place.
*/
std::pair<iterator, bool> insert(const value_type &value) {
return insert_or_do_nothing(value);
}
/*! @copydoc insert */
std::pair<iterator, bool> insert(value_type &&value) {
return insert_or_do_nothing(std::move(value));
}
/**
* @brief Inserts elements into the container, if they do not exist.
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of elements.
* @param last An iterator past the last element of the range of elements.
*/
template<typename It>
void insert(It first, It last) {
for(; first != last; ++first) {
insert(*first);
}
}
/**
* @brief Constructs an element in-place, if it does not exist.
*
* The element is also constructed when the container already has the key,
* in which case the newly constructed object is destroyed immediately.
*
* @tparam Args Types of arguments to forward to the constructor of the
* element.
* @param args Arguments to forward to the constructor of the element.
* @return A pair consisting of an iterator to the inserted element (or to
* the element that prevented the insertion) and a bool denoting whether the
* insertion took place.
*/
template<typename... Args>
std::pair<iterator, bool> emplace(Args &&...args) {
if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v<std::decay_t<Args>, value_type>)) {
return insert_or_do_nothing(std::forward<Args>(args)...);
} else {
auto &node = packed.first().emplace_back(std::piecewise_construct, std::make_tuple(packed.first().size()), std::forward_as_tuple(std::forward<Args>(args)...));
const auto index = value_to_bucket(node.second);
if(auto it = constrained_find(node.second, index); it != end()) {
packed.first().pop_back();
return std::make_pair(it, false);
}
std::swap(node.first, sparse.first()[index]);
rehash_if_required();
return std::make_pair(--end(), true);
}
}
/**
* @brief Removes an element from a given position.
* @param pos An iterator to the element to remove.
* @return An iterator following the removed element.
*/
iterator erase(const_iterator pos) {
const auto diff = pos - cbegin();
erase(*pos);
return begin() + diff;
}
/**
* @brief Removes the given elements from a container.
* @param first An iterator to the first element of the range of elements.
* @param last An iterator past the last element of the range of elements.
* @return An iterator following the last removed element.
*/
iterator erase(const_iterator first, const_iterator last) {
const auto dist = first - cbegin();
for(auto from = last - cbegin(); from != dist; --from) {
erase(packed.first()[static_cast<size_type>(from) - 1u].second);
}
return (begin() + dist);
}
/**
* @brief Removes the element associated with a given value.
* @param value Value of an element to remove.
* @return Number of elements removed (either 0 or 1).
*/
size_type erase(const value_type &value) {
for(size_type *curr = &sparse.first()[value_to_bucket(value)]; *curr != (std::numeric_limits<size_type>::max)(); curr = &packed.first()[*curr].first) {
if(packed.second()(packed.first()[*curr].second, value)) {
const auto index = *curr;
*curr = packed.first()[*curr].first;
move_and_pop(index);
return 1u;
}
}
return 0u;
}
/**
* @brief Returns the number of elements matching a value (either 1 or 0).
* @param key Key value of an element to search for.
* @return Number of elements matching the key (either 1 or 0).
*/
[[nodiscard]] size_type count(const value_type &key) const {
return find(key) != end();
}
/**
* @brief Returns the number of elements matching a key (either 1 or 0).
* @tparam Other Type of the key value of an element to search for.
* @param key Key value of an element to search for.
* @return Number of elements matching the key (either 1 or 0).
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, size_type>>
count(const Other &key) const {
return find(key) != end();
}
/**
* @brief Finds an element with a given value.
* @param value Value of an element to search for.
* @return An iterator to an element with the given value. If no such
* element is found, a past-the-end iterator is returned.
*/
[[nodiscard]] iterator find(const value_type &value) {
return constrained_find(value, value_to_bucket(value));
}
/*! @copydoc find */
[[nodiscard]] const_iterator find(const value_type &value) const {
return constrained_find(value, value_to_bucket(value));
}
/**
* @brief Finds an element that compares _equivalent_ to a given value.
* @tparam Other Type of an element to search for.
* @param value Value of an element to search for.
* @return An iterator to an element with the given value. If no such
* element is found, a past-the-end iterator is returned.
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, iterator>>
find(const Other &value) {
return constrained_find(value, value_to_bucket(value));
}
/*! @copydoc find */
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, const_iterator>>
find(const Other &value) const {
return constrained_find(value, value_to_bucket(value));
}
/**
* @brief Returns a range containing all elements with a given value.
* @param value Value of an element to search for.
* @return A pair of iterators pointing to the first element and past the
* last element of the range.
*/
[[nodiscard]] std::pair<iterator, iterator> equal_range(const value_type &value) {
const auto it = find(value);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
[[nodiscard]] std::pair<const_iterator, const_iterator> equal_range(const value_type &value) const {
const auto it = find(value);
return {it, it + !(it == cend())};
}
/**
* @brief Returns a range containing all elements that compare _equivalent_
* to a given value.
* @tparam Other Type of an element to search for.
* @param value Value of an element to search for.
* @return A pair of iterators pointing to the first element and past the
* last element of the range.
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<iterator, iterator>>>
equal_range(const Other &value) {
const auto it = find(value);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>>
equal_range(const Other &value) const {
const auto it = find(value);
return {it, it + !(it == cend())};
}
/**
* @brief Checks if the container contains an element with a given value.
* @param value Value of an element to search for.
* @return True if there is such an element, false otherwise.
*/
[[nodiscard]] bool contains(const value_type &value) const {
return (find(value) != cend());
}
/**
* @brief Checks if the container contains an element that compares
* _equivalent_ to a given value.
* @tparam Other Type of an element to search for.
* @param value Value of an element to search for.
* @return True if there is such an element, false otherwise.
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, bool>>
contains(const Other &value) const {
return (find(value) != cend());
}
/**
* @brief Returns an iterator to the beginning of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the beginning of the given bucket.
*/
[[nodiscard]] const_local_iterator cbegin(const size_type index) const {
return {packed.first().begin(), sparse.first()[index]};
}
/**
* @brief Returns an iterator to the beginning of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the beginning of the given bucket.
*/
[[nodiscard]] const_local_iterator begin(const size_type index) const {
return cbegin(index);
}
/**
* @brief Returns an iterator to the beginning of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the beginning of the given bucket.
*/
[[nodiscard]] local_iterator begin(const size_type index) {
return {packed.first().begin(), sparse.first()[index]};
}
/**
* @brief Returns an iterator to the end of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the end of the given bucket.
*/
[[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const {
return {packed.first().begin(), (std::numeric_limits<size_type>::max)()};
}
/**
* @brief Returns an iterator to the end of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the end of the given bucket.
*/
[[nodiscard]] const_local_iterator end(const size_type index) const {
return cend(index);
}
/**
* @brief Returns an iterator to the end of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the end of the given bucket.
*/
[[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) {
return {packed.first().begin(), (std::numeric_limits<size_type>::max)()};
}
/**
* @brief Returns the number of buckets.
* @return The number of buckets.
*/
[[nodiscard]] size_type bucket_count() const {
return sparse.first().size();
}
/**
* @brief Returns the maximum number of buckets.
* @return The maximum number of buckets.
*/
[[nodiscard]] size_type max_bucket_count() const {
return sparse.first().max_size();
}
/**
* @brief Returns the number of elements in a given bucket.
* @param index The index of the bucket to examine.
* @return The number of elements in the given bucket.
*/
[[nodiscard]] size_type bucket_size(const size_type index) const {
return static_cast<size_type>(std::distance(begin(index), end(index)));
}
/**
* @brief Returns the bucket for a given element.
* @param value The value of the element to examine.
* @return The bucket for the given element.
*/
[[nodiscard]] size_type bucket(const value_type &value) const {
return value_to_bucket(value);
}
/**
* @brief Returns the average number of elements per bucket.
* @return The average number of elements per bucket.
*/
[[nodiscard]] float load_factor() const {
return static_cast<float>(size()) / static_cast<float>(bucket_count());
}
/**
* @brief Returns the maximum average number of elements per bucket.
* @return The maximum average number of elements per bucket.
*/
[[nodiscard]] float max_load_factor() const {
return threshold;
}
/**
* @brief Sets the desired maximum average number of elements per bucket.
* @param value A desired maximum average number of elements per bucket.
*/
void max_load_factor(const float value) {
ENTT_ASSERT(value > 0.f, "Invalid load factor");
threshold = value;
rehash(0u);
}
/**
* @brief Reserves at least the specified number of buckets and regenerates
* the hash table.
* @param cnt New number of buckets.
*/
void rehash(const size_type cnt) {
auto value = cnt > minimum_capacity ? cnt : minimum_capacity;
const auto cap = static_cast<size_type>(static_cast<float>(size()) / max_load_factor());
value = value > cap ? value : cap;
if(const auto sz = next_power_of_two(value); sz != bucket_count()) {
sparse.first().resize(sz);
for(auto &&elem: sparse.first()) {
elem = (std::numeric_limits<size_type>::max)();
}
for(size_type pos{}, last = size(); pos < last; ++pos) {
const auto index = value_to_bucket(packed.first()[pos].second);
packed.first()[pos].first = std::exchange(sparse.first()[index], pos);
}
}
}
/**
* @brief Reserves space for at least the specified number of elements and
* regenerates the hash table.
* @param cnt New number of elements.
*/
void reserve(const size_type cnt) {
packed.first().reserve(cnt);
rehash(static_cast<size_type>(std::ceil(static_cast<float>(cnt) / max_load_factor())));
}
/**
* @brief Returns the function used to hash the elements.
* @return The function used to hash the elements.
*/
[[nodiscard]] hasher hash_function() const {
return sparse.second();
}
/**
* @brief Returns the function used to compare elements for equality.
* @return The function used to compare elements for equality.
*/
[[nodiscard]] key_equal key_eq() const {
return packed.second();
}
private:
compressed_pair<sparse_container_type, hasher> sparse;
compressed_pair<packed_container_type, key_equal> packed;
float threshold{default_threshold};
};
} // namespace entt
#endif

View File

@@ -0,0 +1,38 @@
#ifndef ENTT_CONTAINER_FWD_HPP
#define ENTT_CONTAINER_FWD_HPP
#include <functional>
#include <memory>
#include <utility>
#include <vector>
namespace entt {
template<
typename Key,
typename Type,
typename = std::hash<Key>,
typename = std::equal_to<>,
typename = std::allocator<std::pair<const Key, Type>>>
class dense_map;
template<
typename Type,
typename = std::hash<Type>,
typename = std::equal_to<>,
typename = std::allocator<Type>>
class dense_set;
template<typename...>
class basic_table;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Element types.
*/
template<typename... Type>
using table = basic_table<std::vector<Type>...>;
} // namespace entt
#endif

View File

@@ -0,0 +1,462 @@
#ifndef ENTT_CONTAINER_TABLE_HPP
#define ENTT_CONTAINER_TABLE_HPP
#include <cstddef>
#include <iterator>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/iterator.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename... It>
class table_iterator {
template<typename...>
friend class table_iterator;
public:
using value_type = decltype(std::forward_as_tuple(*std::declval<It>()...));
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
using iterator_concept = std::random_access_iterator_tag;
constexpr table_iterator() noexcept
: it{} {}
constexpr table_iterator(It... from) noexcept
: it{from...} {}
template<typename... Other, typename = std::enable_if_t<(std::is_constructible_v<It, Other> && ...)>>
constexpr table_iterator(const table_iterator<Other...> &other) noexcept
: table_iterator{std::get<Other>(other.it)...} {}
constexpr table_iterator &operator++() noexcept {
return (++std::get<It>(it), ...), *this;
}
constexpr table_iterator operator++(int) noexcept {
const table_iterator orig = *this;
return ++(*this), orig;
}
constexpr table_iterator &operator--() noexcept {
return (--std::get<It>(it), ...), *this;
}
constexpr table_iterator operator--(int) noexcept {
const table_iterator orig = *this;
return operator--(), orig;
}
constexpr table_iterator &operator+=(const difference_type value) noexcept {
return ((std::get<It>(it) += value), ...), *this;
}
constexpr table_iterator operator+(const difference_type value) const noexcept {
table_iterator copy = *this;
return (copy += value);
}
constexpr table_iterator &operator-=(const difference_type value) noexcept {
return (*this += -value);
}
constexpr table_iterator operator-(const difference_type value) const noexcept {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
return std::forward_as_tuple(std::get<It>(it)[value]...);
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return {operator[](0)};
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return operator[](0);
}
template<typename... Lhs, typename... Rhs>
friend constexpr std::ptrdiff_t operator-(const table_iterator<Lhs...> &, const table_iterator<Rhs...> &) noexcept;
template<typename... Lhs, typename... Rhs>
friend constexpr bool operator==(const table_iterator<Lhs...> &, const table_iterator<Rhs...> &) noexcept;
template<typename... Lhs, typename... Rhs>
friend constexpr bool operator<(const table_iterator<Lhs...> &, const table_iterator<Rhs...> &) noexcept;
private:
std::tuple<It...> it;
};
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return std::get<0>(lhs.it) - std::get<0>(rhs.it);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator==(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return std::get<0>(lhs.it) == std::get<0>(rhs.it);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator!=(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return !(lhs == rhs);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return std::get<0>(lhs.it) < std::get<0>(rhs.it);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return rhs < lhs;
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<=(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return !(lhs > rhs);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>=(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return !(lhs < rhs);
}
} // namespace internal
/*! @endcond */
/**
* @brief Basic table implementation.
*
* Internal data structures arrange elements to maximize performance. There are
* no guarantees that objects are returned in the insertion order when iterate
* a table. Do not make assumption on the order in any case.
*
* @tparam Container Sequence container row types.
*/
template<typename... Container>
class basic_table {
using container_type = std::tuple<Container...>;
public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Signed integer type. */
using difference_type = std::ptrdiff_t;
/*! @brief Input iterator type. */
using iterator = internal::table_iterator<typename Container::iterator...>;
/*! @brief Constant input iterator type. */
using const_iterator = internal::table_iterator<typename Container::const_iterator...>;
/*! @brief Reverse iterator type. */
using reverse_iterator = internal::table_iterator<typename Container::reverse_iterator...>;
/*! @brief Constant reverse iterator type. */
using const_reverse_iterator = internal::table_iterator<typename Container::const_reverse_iterator...>;
/*! @brief Default constructor. */
basic_table()
: payload{} {
}
/**
* @brief Copy constructs the underlying containers.
* @param container The containers to copy from.
*/
explicit basic_table(const Container &...container) noexcept
: payload{container...} {
ENTT_ASSERT((((std::get<Container>(payload).size() * sizeof...(Container)) == (std::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/**
* @brief Move constructs the underlying containers.
* @param container The containers to move from.
*/
explicit basic_table(Container &&...container) noexcept
: payload{std::move(container)...} {
ENTT_ASSERT((((std::get<Container>(payload).size() * sizeof...(Container)) == (std::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_table(const basic_table &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_table(basic_table &&other) noexcept
: payload{std::move(other.payload)} {}
/**
* @brief Constructs the underlying containers using a given allocator.
* @tparam Allocator Type of allocator.
* @param allocator A valid allocator.
*/
template<typename Allocator>
explicit basic_table(const Allocator &allocator)
: payload{Container{allocator}...} {}
/**
* @brief Copy constructs the underlying containers using a given allocator.
* @tparam Allocator Type of allocator.
* @param container The containers to copy from.
* @param allocator A valid allocator.
*/
template<class Allocator>
basic_table(const Container &...container, const Allocator &allocator) noexcept
: payload{Container{container, allocator}...} {
ENTT_ASSERT((((std::get<Container>(payload).size() * sizeof...(Container)) == (std::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/**
* @brief Move constructs the underlying containers using a given allocator.
* @tparam Allocator Type of allocator.
* @param container The containers to move from.
* @param allocator A valid allocator.
*/
template<class Allocator>
basic_table(Container &&...container, const Allocator &allocator) noexcept
: payload{Container{std::move(container), allocator}...} {
ENTT_ASSERT((((std::get<Container>(payload).size() * sizeof...(Container)) == (std::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/**
* @brief Allocator-extended move constructor.
* @tparam Allocator Type of allocator.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
template<class Allocator>
basic_table(basic_table &&other, const Allocator &allocator)
: payload{Container{std::move(std::get<Container>(other.payload)), allocator}...} {}
/*! @brief Default destructor. */
~basic_table() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This container.
*/
basic_table &operator=(const basic_table &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This container.
*/
basic_table &operator=(basic_table &&other) noexcept {
swap(other);
return *this;
}
/**
* @brief Exchanges the contents with those of a given table.
* @param other Table to exchange the content with.
*/
void swap(basic_table &other) noexcept {
using std::swap;
swap(payload, other.payload);
}
/**
* @brief Increases the capacity of a table.
*
* If the new capacity is greater than the current capacity, new storage is
* allocated, otherwise the method does nothing.
*
* @param cap Desired capacity.
*/
void reserve(const size_type cap) {
(std::get<Container>(payload).reserve(cap), ...);
}
/**
* @brief Returns the number of rows that a table has currently allocated
* space for.
* @return Capacity of the table.
*/
[[nodiscard]] size_type capacity() const noexcept {
return std::get<0>(payload).capacity();
}
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() {
(std::get<Container>(payload).shrink_to_fit(), ...);
}
/**
* @brief Returns the number of rows in a table.
* @return Number of rows.
*/
[[nodiscard]] size_type size() const noexcept {
return std::get<0>(payload).size();
}
/**
* @brief Checks whether a table is empty.
* @return True if the table is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
return std::get<0>(payload).empty();
}
/**
* @brief Returns an iterator to the beginning.
*
* If the table is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first row of the table.
*/
[[nodiscard]] const_iterator cbegin() const noexcept {
return {std::get<Container>(payload).cbegin()...};
}
/*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept {
return cbegin();
}
/*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept {
return {std::get<Container>(payload).begin()...};
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last row of the table.
*/
[[nodiscard]] const_iterator cend() const noexcept {
return {std::get<Container>(payload).cend()...};
}
/*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept {
return cend();
}
/*! @copydoc end */
[[nodiscard]] iterator end() noexcept {
return {std::get<Container>(payload).end()...};
}
/**
* @brief Returns a reverse iterator to the beginning.
*
* If the table is empty, the returned iterator will be equal to `rend()`.
*
* @return An iterator to the first row of the reversed table.
*/
[[nodiscard]] const_reverse_iterator crbegin() const noexcept {
return {std::get<Container>(payload).crbegin()...};
}
/*! @copydoc crbegin */
[[nodiscard]] const_reverse_iterator rbegin() const noexcept {
return crbegin();
}
/*! @copydoc rbegin */
[[nodiscard]] reverse_iterator rbegin() noexcept {
return {std::get<Container>(payload).rbegin()...};
}
/**
* @brief Returns a reverse iterator to the end.
* @return An iterator to the element following the last row of the reversed
* table.
*/
[[nodiscard]] const_reverse_iterator crend() const noexcept {
return {std::get<Container>(payload).crend()...};
}
/*! @copydoc crend */
[[nodiscard]] const_reverse_iterator rend() const noexcept {
return crend();
}
/*! @copydoc rend */
[[nodiscard]] reverse_iterator rend() noexcept {
return {std::get<Container>(payload).rend()...};
}
/**
* @brief Appends a row to the end of a table.
* @tparam Args Types of arguments to use to construct the row data.
* @param args Parameters to use to construct the row data.
* @return A reference to the newly created row data.
*/
template<typename... Args>
std::tuple<typename Container::value_type &...> emplace(Args &&...args) {
if constexpr(sizeof...(Args) == 0u) {
return std::forward_as_tuple(std::get<Container>(payload).emplace_back()...);
} else {
return std::forward_as_tuple(std::get<Container>(payload).emplace_back(std::forward<Args>(args))...);
}
}
/**
* @brief Removes a row from a table.
* @param pos An iterator to the row to remove.
* @return An iterator following the removed row.
*/
iterator erase(const_iterator pos) {
const auto diff = pos - begin();
return {std::get<Container>(payload).erase(std::get<Container>(payload).begin() + diff)...};
}
/**
* @brief Removes a row from a table.
* @param pos Index of the row to remove.
*/
void erase(const size_type pos) {
ENTT_ASSERT(pos < size(), "Index out of bounds");
erase(begin() + static_cast<difference_type>(pos));
}
/**
* @brief Returns the row data at specified location.
* @param pos The row for which to return the data.
* @return The row data at specified location.
*/
[[nodiscard]] std::tuple<const typename Container::value_type &...> operator[](const size_type pos) const {
ENTT_ASSERT(pos < size(), "Index out of bounds");
return std::forward_as_tuple(std::get<Container>(payload)[pos]...);
}
/*! @copydoc operator[] */
[[nodiscard]] std::tuple<typename Container::value_type &...> operator[](const size_type pos) {
ENTT_ASSERT(pos < size(), "Index out of bounds");
return std::forward_as_tuple(std::get<Container>(payload)[pos]...);
}
/*! @brief Clears a table. */
void clear() {
(std::get<Container>(payload).clear(), ...);
}
private:
container_type payload;
};
} // namespace entt
/*! @cond TURN_OFF_DOXYGEN */
namespace std {
template<typename... Container, typename Allocator>
struct uses_allocator<entt::basic_table<Container...>, Allocator>
: std::bool_constant<(std::uses_allocator_v<Container, Allocator> && ...)> {};
} // namespace std
/*! @endcond */
#endif

View File

@@ -0,0 +1,145 @@
#ifndef ENTT_CORE_ALGORITHM_HPP
#define ENTT_CORE_ALGORITHM_HPP
#include <algorithm>
#include <functional>
#include <iterator>
#include <utility>
#include <vector>
#include "utility.hpp"
namespace entt {
/**
* @brief Function object to wrap `std::sort` in a class type.
*
* Unfortunately, `std::sort` cannot be passed as template argument to a class
* template or a function template.<br/>
* This class fills the gap by wrapping some flavors of `std::sort` in a
* function object.
*/
struct std_sort {
/**
* @brief Sorts the elements in a range.
*
* Sorts the elements in a range using the given binary comparison function.
*
* @tparam It Type of random access iterator.
* @tparam Compare Type of comparison function object.
* @tparam Args Types of arguments to forward to the sort function.
* @param first An iterator to the first element of the range to sort.
* @param last An iterator past the last element of the range to sort.
* @param compare A valid comparison function object.
* @param args Arguments to forward to the sort function, if any.
*/
template<typename It, typename Compare = std::less<>, typename... Args>
void operator()(It first, It last, Compare compare = Compare{}, Args &&...args) const {
std::sort(std::forward<Args>(args)..., std::move(first), std::move(last), std::move(compare));
}
};
/*! @brief Function object for performing insertion sort. */
struct insertion_sort {
/**
* @brief Sorts the elements in a range.
*
* Sorts the elements in a range using the given binary comparison function.
*
* @tparam It Type of random access iterator.
* @tparam Compare Type of comparison function object.
* @param first An iterator to the first element of the range to sort.
* @param last An iterator past the last element of the range to sort.
* @param compare A valid comparison function object.
*/
template<typename It, typename Compare = std::less<>>
void operator()(It first, It last, Compare compare = Compare{}) const {
if(first < last) {
for(auto it = first + 1; it < last; ++it) {
auto value = std::move(*it);
auto pre = it;
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(; pre > first && compare(value, *(pre - 1)); --pre) {
*pre = std::move(*(pre - 1));
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
*pre = std::move(value);
}
}
}
};
/**
* @brief Function object for performing LSD radix sort.
* @tparam Bit Number of bits processed per pass.
* @tparam N Maximum number of bits to sort.
*/
template<std::size_t Bit, std::size_t N>
struct radix_sort {
static_assert((N % Bit) == 0, "The maximum number of bits to sort must be a multiple of the number of bits processed per pass");
/**
* @brief Sorts the elements in a range.
*
* Sorts the elements in a range using the given _getter_ to access the
* actual data to be sorted.
*
* This implementation is inspired by the online book
* [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies.html#RadixSort).
*
* @tparam It Type of random access iterator.
* @tparam Getter Type of _getter_ function object.
* @param first An iterator to the first element of the range to sort.
* @param last An iterator past the last element of the range to sort.
* @param getter A valid _getter_ function object.
*/
template<typename It, typename Getter = identity>
void operator()(It first, It last, Getter getter = Getter{}) const {
if(first < last) {
constexpr auto passes = N / Bit;
using value_type = typename std::iterator_traits<It>::value_type;
using difference_type = typename std::iterator_traits<It>::difference_type;
std::vector<value_type> aux(static_cast<std::size_t>(std::distance(first, last)));
auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) {
constexpr auto mask = (1 << Bit) - 1;
constexpr auto buckets = 1 << Bit;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays, misc-const-correctness)
std::size_t count[buckets]{};
for(auto it = from; it != to; ++it) {
++count[(getter(*it) >> start) & mask];
}
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
std::size_t index[buckets]{};
for(std::size_t pos{}, end = buckets - 1u; pos < end; ++pos) {
index[pos + 1u] = index[pos] + count[pos];
}
for(auto it = from; it != to; ++it) {
const auto pos = index[(getter(*it) >> start) & mask]++;
out[static_cast<difference_type>(pos)] = std::move(*it);
}
};
for(std::size_t pass = 0; pass < (passes & ~1u); pass += 2) {
part(first, last, aux.begin(), pass * Bit);
part(aux.begin(), aux.end(), first, (pass + 1) * Bit);
}
if constexpr(passes & 1) {
part(first, last, aux.begin(), (passes - 1) * Bit);
std::move(aux.begin(), aux.end(), first);
}
}
}
};
} // namespace entt
#endif

544
src/external/entt/src/entt/core/any.hpp vendored Normal file
View File

@@ -0,0 +1,544 @@
#ifndef ENTT_CORE_ANY_HPP
#define ENTT_CORE_ANY_HPP
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/utility.hpp"
#include "fwd.hpp"
#include "type_info.hpp"
#include "type_traits.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
enum class any_request : std::uint8_t {
transfer,
assign,
destroy,
compare,
copy,
move,
get
};
} // namespace internal
/*! @endcond */
/**
* @brief A SBO friendly, type-safe container for single values of any type.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
*/
template<std::size_t Len, std::size_t Align>
class basic_any {
using request = internal::any_request;
using vtable_type = const void *(const request, const basic_any &, const void *);
struct storage_type {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
alignas(Align) std::byte data[Len + static_cast<std::size_t>(Len == 0u)];
};
template<typename Type>
// NOLINTNEXTLINE(bugprone-sizeof-expression)
static constexpr bool in_situ = (Len != 0u) && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v<Type>;
template<typename Type>
static const void *basic_vtable(const request req, const basic_any &value, const void *other) {
static_assert(!std::is_void_v<Type> && std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, Type>, "Invalid type");
const Type *elem = nullptr;
if constexpr(in_situ<Type>) {
elem = (value.mode == any_policy::embedded) ? reinterpret_cast<const Type *>(&value.storage) : static_cast<const Type *>(value.instance);
} else {
elem = static_cast<const Type *>(value.instance);
}
switch(req) {
case request::transfer:
if constexpr(std::is_move_assignable_v<Type>) {
// NOLINTNEXTLINE(bugprone-casting-through-void)
*const_cast<Type *>(elem) = std::move(*static_cast<Type *>(const_cast<void *>(other)));
return other;
}
[[fallthrough]];
case request::assign:
if constexpr(std::is_copy_assignable_v<Type>) {
*const_cast<Type *>(elem) = *static_cast<const Type *>(other);
return other;
}
break;
case request::destroy:
if constexpr(in_situ<Type>) {
(value.mode == any_policy::embedded) ? elem->~Type() : (delete elem);
} else if constexpr(std::is_array_v<Type>) {
delete[] elem;
} else {
delete elem;
}
break;
case request::compare:
if constexpr(!std::is_function_v<Type> && !std::is_array_v<Type> && is_equality_comparable_v<Type>) {
return (*elem == *static_cast<const Type *>(other)) ? other : nullptr;
} else {
return (elem == other) ? other : nullptr;
}
case request::copy:
if constexpr(std::is_copy_constructible_v<Type>) {
// NOLINTNEXTLINE(bugprone-casting-through-void)
static_cast<basic_any *>(const_cast<void *>(other))->initialize<Type>(*elem);
}
break;
case request::move:
ENTT_ASSERT(value.mode == any_policy::embedded, "Unexpected policy");
if constexpr(in_situ<Type>) {
// NOLINTNEXTLINE(bugprone-casting-through-void, bugprone-multi-level-implicit-pointer-conversion)
return ::new(&static_cast<basic_any *>(const_cast<void *>(other))->storage) Type{std::move(*const_cast<Type *>(elem))};
}
[[fallthrough]];
case request::get:
ENTT_ASSERT(value.mode == any_policy::embedded, "Unexpected policy");
if constexpr(in_situ<Type>) {
// NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
return elem;
}
}
return nullptr;
}
template<typename Type, typename... Args>
void initialize([[maybe_unused]] Args &&...args) {
if constexpr(!std::is_void_v<Type>) {
using plain_type = std::remove_cv_t<std::remove_reference_t<Type>>;
info = &type_id<plain_type>();
vtable = basic_vtable<plain_type>;
if constexpr(std::is_lvalue_reference_v<Type>) {
static_assert((std::is_lvalue_reference_v<Args> && ...) && (sizeof...(Args) == 1u), "Invalid arguments");
mode = std::is_const_v<std::remove_reference_t<Type>> ? any_policy::cref : any_policy::ref;
// NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
instance = (std::addressof(args), ...);
} else if constexpr(in_situ<plain_type>) {
mode = any_policy::embedded;
if constexpr(std::is_aggregate_v<plain_type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<plain_type>)) {
::new(&storage) plain_type{std::forward<Args>(args)...};
} else {
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
::new(&storage) plain_type(std::forward<Args>(args)...);
}
} else {
mode = any_policy::dynamic;
if constexpr(std::is_aggregate_v<plain_type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<plain_type>)) {
instance = new plain_type{std::forward<Args>(args)...};
} else if constexpr(std::is_array_v<plain_type>) {
static_assert(sizeof...(Args) == 0u, "Invalid arguments");
instance = new plain_type[std::extent_v<plain_type>]();
} else {
instance = new plain_type(std::forward<Args>(args)...);
}
}
}
}
basic_any(const basic_any &other, const any_policy pol) noexcept
: instance{other.data()},
info{other.info},
vtable{other.vtable},
mode{pol} {}
public:
/*! @brief Size of the internal storage. */
static constexpr auto length = Len;
/*! @brief Alignment requirement. */
static constexpr auto alignment = Align;
/*! @brief Default constructor. */
constexpr basic_any() noexcept
: basic_any{std::in_place_type<void>} {}
/**
* @brief Constructs a wrapper by directly initializing the new object.
* @tparam Type Type of object to use to initialize the wrapper.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<typename Type, typename... Args>
explicit basic_any(std::in_place_type_t<Type>, Args &&...args)
: instance{} {
initialize<Type>(std::forward<Args>(args)...);
}
/**
* @brief Constructs a wrapper taking ownership of the passed object.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value A pointer to an object to take ownership of.
*/
template<typename Type>
explicit basic_any(std::in_place_t, Type *value)
: instance{} {
static_assert(!std::is_const_v<Type> && !std::is_void_v<Type>, "Non-const non-void pointer required");
if(value != nullptr) {
initialize<Type &>(*value);
mode = any_policy::dynamic;
}
}
/**
* @brief Constructs a wrapper from a given value.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value An instance of an object to use to initialize the wrapper.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>>>
basic_any(Type &&value)
: basic_any{std::in_place_type<std::decay_t<Type>>, std::forward<Type>(value)} {}
/**
* @brief Copy constructor.
* @param other The instance to copy from.
*/
basic_any(const basic_any &other)
: basic_any{} {
if(other.vtable) {
other.vtable(request::copy, other, this);
}
}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_any(basic_any &&other) noexcept
: instance{},
info{other.info},
vtable{other.vtable},
mode{other.mode} {
if(other.mode == any_policy::embedded) {
other.vtable(request::move, other, this);
} else if(other.mode != any_policy::empty) {
instance = std::exchange(other.instance, nullptr);
}
}
/*! @brief Frees the internal storage, whatever it means. */
~basic_any() {
if(owner()) {
vtable(request::destroy, *this, nullptr);
}
}
/**
* @brief Copy assignment operator.
* @param other The instance to copy from.
* @return This any object.
*/
basic_any &operator=(const basic_any &other) {
if(this != &other) {
reset();
if(other.vtable) {
other.vtable(request::copy, other, this);
}
}
return *this;
}
/**
* @brief Move assignment operator.
*
* @warning
* Self-moving puts objects in a safe but unspecified state.
*
* @param other The instance to move from.
* @return This any object.
*/
basic_any &operator=(basic_any &&other) noexcept {
reset();
if(other.mode == any_policy::embedded) {
other.vtable(request::move, other, this);
} else if(other.mode != any_policy::empty) {
instance = std::exchange(other.instance, nullptr);
}
info = other.info;
vtable = other.vtable;
mode = other.mode;
return *this;
}
/**
* @brief Value assignment operator.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value An instance of an object to use to initialize the wrapper.
* @return This any object.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>>>
basic_any &operator=(Type &&value) {
emplace<std::decay_t<Type>>(std::forward<Type>(value));
return *this;
}
/**
* @brief Returns the object type if any, `type_id<void>()` otherwise.
* @return The object type if any, `type_id<void>()` otherwise.
*/
[[nodiscard]] const type_info &type() const noexcept {
return (info == nullptr) ? type_id<void>() : *info;
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] const void *data() const noexcept {
return (mode == any_policy::embedded) ? vtable(request::get, *this, nullptr) : instance;
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @param req Expected type.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] const void *data(const type_info &req) const noexcept {
return (type() == req) ? data() : nullptr;
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] void *data() noexcept {
return mode == any_policy::cref ? nullptr : const_cast<void *>(std::as_const(*this).data());
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @param req Expected type.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] void *data(const type_info &req) noexcept {
return mode == any_policy::cref ? nullptr : const_cast<void *>(std::as_const(*this).data(req));
}
/**
* @brief Replaces the contained object by creating a new instance directly.
* @tparam Type Type of object to use to initialize the wrapper.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<typename Type, typename... Args>
void emplace(Args &&...args) {
reset();
initialize<Type>(std::forward<Args>(args)...);
}
/**
* @brief Assigns a value to the contained object without replacing it.
* @param other The value to assign to the contained object.
* @return True in case of success, false otherwise.
*/
bool assign(const basic_any &other) {
if(vtable && mode != any_policy::cref && *info == other.type()) {
return (vtable(request::assign, *this, other.data()) != nullptr);
}
return false;
}
/*! @copydoc assign */
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
bool assign(basic_any &&other) {
if(vtable && mode != any_policy::cref && *info == other.type()) {
if(auto *val = other.data(); val) {
return (vtable(request::transfer, *this, val) != nullptr);
}
return (vtable(request::assign, *this, std::as_const(other).data()) != nullptr);
}
return false;
}
/*! @brief Destroys contained object */
void reset() {
if(owner()) {
vtable(request::destroy, *this, nullptr);
}
instance = nullptr;
info = nullptr;
vtable = nullptr;
mode = any_policy::empty;
}
/**
* @brief Returns false if a wrapper is empty, true otherwise.
* @return False if the wrapper is empty, true otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return vtable != nullptr;
}
/**
* @brief Checks if two wrappers differ in their content.
* @param other Wrapper with which to compare.
* @return False if the two objects differ in their content, true otherwise.
*/
[[nodiscard]] bool operator==(const basic_any &other) const noexcept {
if(vtable && *info == other.type()) {
return (vtable(request::compare, *this, other.data()) != nullptr);
}
return (!vtable && !other.vtable);
}
/**
* @brief Checks if two wrappers differ in their content.
* @param other Wrapper with which to compare.
* @return True if the two objects differ in their content, false otherwise.
*/
[[nodiscard]] bool operator!=(const basic_any &other) const noexcept {
return !(*this == other);
}
/**
* @brief Aliasing constructor.
* @return A wrapper that shares a reference to an unmanaged object.
*/
[[nodiscard]] basic_any as_ref() noexcept {
return basic_any{*this, (mode == any_policy::cref ? any_policy::cref : any_policy::ref)};
}
/*! @copydoc as_ref */
[[nodiscard]] basic_any as_ref() const noexcept {
return basic_any{*this, any_policy::cref};
}
/**
* @brief Returns true if a wrapper owns its object, false otherwise.
* @return True if the wrapper owns its object, false otherwise.
*/
[[nodiscard]] bool owner() const noexcept {
return (mode == any_policy::dynamic || mode == any_policy::embedded);
}
/**
* @brief Returns the current mode of an any object.
* @return The current mode of the any object.
*/
[[nodiscard]] any_policy policy() const noexcept {
return mode;
}
private:
union {
const void *instance;
storage_type storage;
};
const type_info *info{};
vtable_type *vtable{};
any_policy mode{any_policy::empty};
};
/**
* @brief Performs type-safe access to the contained object.
* @tparam Type Type to which conversion is required.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Alignment requirement.
* @param data Target any object.
* @return The element converted to the requested type.
*/
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] std::remove_const_t<Type> any_cast(const basic_any<Len, Align> &data) noexcept {
const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance);
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] std::remove_const_t<Type> any_cast(basic_any<Len, Align> &data) noexcept {
// forces const on non-reference types to make them work also with wrappers for const references
auto *const instance = any_cast<std::remove_reference_t<const Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance);
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
[[nodiscard]] std::remove_const_t<Type> any_cast(basic_any<Len, Align> &&data) noexcept {
if constexpr(std::is_copy_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) {
return static_cast<Type>(std::move(*instance));
}
return any_cast<Type>(data);
} else {
auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(std::move(*instance));
}
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] const Type *any_cast(const basic_any<Len, Align> *data) noexcept {
const auto &info = type_id<std::remove_cv_t<Type>>();
return static_cast<const Type *>(data->data(info));
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type *any_cast(basic_any<Len, Align> *data) noexcept {
if constexpr(std::is_const_v<Type>) {
// last attempt to make wrappers for const references return their values
return any_cast<Type>(&std::as_const(*data));
} else {
const auto &info = type_id<std::remove_cv_t<Type>>();
return static_cast<Type *>(data->data(info));
}
}
/**
* @brief Constructs a wrapper from a given type, passing it all arguments.
* @tparam Type Type of object to use to initialize the wrapper.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
* @return A properly initialized wrapper for an object of the given type.
*/
template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
[[nodiscard]] basic_any<Len, Align> make_any(Args &&...args) {
return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...};
}
/**
* @brief Forwards its argument and avoids copies for lvalue references.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
* @tparam Type Type of argument to use to construct the new instance.
* @param value Parameter to use to construct the instance.
* @return A properly initialized and not necessarily owning wrapper.
*/
template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
[[nodiscard]] basic_any<Len, Align> forward_as_any(Type &&value) {
return basic_any<Len, Align>{std::in_place_type<Type &&>, std::forward<Type>(value)};
}
} // namespace entt
#endif

View File

@@ -0,0 +1,30 @@
#ifndef ENTT_CORE_ATTRIBUTE_H
#define ENTT_CORE_ATTRIBUTE_H
#ifndef ENTT_EXPORT
# if defined _WIN32 || defined __CYGWIN__ || defined _MSC_VER
# define ENTT_EXPORT __declspec(dllexport)
# define ENTT_IMPORT __declspec(dllimport)
# define ENTT_HIDDEN
# elif defined __GNUC__ && __GNUC__ >= 4
# define ENTT_EXPORT __attribute__((visibility("default")))
# define ENTT_IMPORT __attribute__((visibility("default")))
# define ENTT_HIDDEN __attribute__((visibility("hidden")))
# else /* Unsupported compiler */
# define ENTT_EXPORT
# define ENTT_IMPORT
# define ENTT_HIDDEN
# endif
#endif
#ifndef ENTT_API
# if defined ENTT_API_EXPORT
# define ENTT_API ENTT_EXPORT
# elif defined ENTT_API_IMPORT
# define ENTT_API ENTT_IMPORT
# else /* No API */
# define ENTT_API
# endif
#endif
#endif

70
src/external/entt/src/entt/core/bit.hpp vendored Normal file
View File

@@ -0,0 +1,70 @@
#ifndef ENTT_CORE_BIT_HPP
#define ENTT_CORE_BIT_HPP
#include <cstddef>
#include <limits>
#include <type_traits>
#include "../config/config.h"
namespace entt {
/**
* @brief Returns the number of set bits in a value (waiting for C++20 and
* `std::popcount`).
* @tparam Type Unsigned integer type.
* @param value A value of unsigned integer type.
* @return The number of set bits in the value.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<std::is_unsigned_v<Type>, int> popcount(const Type value) noexcept {
return value ? (int(value & 1) + popcount(static_cast<Type>(value >> 1))) : 0;
}
/**
* @brief Checks whether a value is a power of two or not (waiting for C++20 and
* `std::has_single_bit`).
* @tparam Type Unsigned integer type.
* @param value A value of unsigned integer type.
* @return True if the value is a power of two, false otherwise.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<std::is_unsigned_v<Type>, bool> has_single_bit(const Type value) noexcept {
return value && ((value & (value - 1)) == 0);
}
/**
* @brief Computes the smallest power of two greater than or equal to a value
* (waiting for C++20 and `std::bit_ceil`).
* @tparam Type Unsigned integer type.
* @param value A value of unsigned integer type.
* @return The smallest power of two greater than or equal to the given value.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<std::is_unsigned_v<Type>, Type> next_power_of_two(const Type value) noexcept {
// NOLINTNEXTLINE(bugprone-assert-side-effect)
ENTT_ASSERT_CONSTEXPR(value < (Type{1u} << (std::numeric_limits<Type>::digits - 1)), "Numeric limits exceeded");
Type curr = value - (value != 0u);
for(int next = 1; next < std::numeric_limits<Type>::digits; next = next * 2) {
curr |= (curr >> next);
}
return ++curr;
}
/**
* @brief Fast module utility function (powers of two only).
* @tparam Type Unsigned integer type.
* @param value A value of unsigned integer type.
* @param mod _Modulus_, it must be a power of two.
* @return The common remainder.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<std::is_unsigned_v<Type>, Type> fast_mod(const Type value, const std::size_t mod) noexcept {
ENTT_ASSERT_CONSTEXPR(has_single_bit(mod), "Value must be a power of two");
return static_cast<Type>(value & (mod - 1u));
}
} // namespace entt
#endif

View File

@@ -0,0 +1,272 @@
#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP
#define ENTT_CORE_COMPRESSED_PAIR_HPP
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
#include "fwd.hpp"
#include "type_traits.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Type, std::size_t, typename = void>
struct compressed_pair_element {
using reference = Type &;
using const_reference = const Type &;
template<typename Dummy = Type, typename = std::enable_if_t<std::is_default_constructible_v<Dummy>>>
// NOLINTNEXTLINE(modernize-use-equals-default)
constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v<Type>) {}
template<typename Arg, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Arg>>, compressed_pair_element>>>
constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v<Type, Arg>)
: value{std::forward<Arg>(arg)} {}
template<typename... Args, std::size_t... Index>
constexpr compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>) noexcept(std::is_nothrow_constructible_v<Type, Args...>)
: value{std::forward<Args>(std::get<Index>(args))...} {}
[[nodiscard]] constexpr reference get() noexcept {
return value;
}
[[nodiscard]] constexpr const_reference get() const noexcept {
return value;
}
private:
Type value{};
};
template<typename Type, std::size_t Tag>
struct compressed_pair_element<Type, Tag, std::enable_if_t<is_ebco_eligible_v<Type>>>: Type {
using reference = Type &;
using const_reference = const Type &;
using base_type = Type;
template<typename Dummy = Type, typename = std::enable_if_t<std::is_default_constructible_v<Dummy>>>
constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v<base_type>)
: base_type{} {}
template<typename Arg, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Arg>>, compressed_pair_element>>>
constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v<base_type, Arg>)
: base_type{std::forward<Arg>(arg)} {}
template<typename... Args, std::size_t... Index>
constexpr compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>) noexcept(std::is_nothrow_constructible_v<base_type, Args...>)
: base_type{std::forward<Args>(std::get<Index>(args))...} {}
[[nodiscard]] constexpr reference get() noexcept {
return *this;
}
[[nodiscard]] constexpr const_reference get() const noexcept {
return *this;
}
};
} // namespace internal
/*! @endcond */
/**
* @brief A compressed pair.
*
* A pair that exploits the _Empty Base Class Optimization_ (or _EBCO_) to
* reduce its final size to a minimum.
*
* @tparam First The type of the first element that the pair stores.
* @tparam Second The type of the second element that the pair stores.
*/
template<typename First, typename Second>
class compressed_pair final
: internal::compressed_pair_element<First, 0u>,
internal::compressed_pair_element<Second, 1u> {
using first_base = internal::compressed_pair_element<First, 0u>;
using second_base = internal::compressed_pair_element<Second, 1u>;
public:
/*! @brief The type of the first element that the pair stores. */
using first_type = First;
/*! @brief The type of the second element that the pair stores. */
using second_type = Second;
/**
* @brief Default constructor, conditionally enabled.
*
* This constructor is only available when the types that the pair stores
* are both at least default constructible.
*
* @tparam Dummy Dummy template parameter used for internal purposes.
*/
template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<first_type> && std::is_default_constructible_v<second_type>>>
constexpr compressed_pair() noexcept(std::is_nothrow_default_constructible_v<first_base> && std::is_nothrow_default_constructible_v<second_base>)
: first_base{},
second_base{} {}
/**
* @brief Copy constructor.
* @param other The instance to copy from.
*/
constexpr compressed_pair(const compressed_pair &other) = default;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
constexpr compressed_pair(compressed_pair &&other) noexcept = default;
/**
* @brief Constructs a pair from its values.
* @tparam Arg Type of value to use to initialize the first element.
* @tparam Other Type of value to use to initialize the second element.
* @param arg Value to use to initialize the first element.
* @param other Value to use to initialize the second element.
*/
template<typename Arg, typename Other>
constexpr compressed_pair(Arg &&arg, Other &&other) noexcept(std::is_nothrow_constructible_v<first_base, Arg> && std::is_nothrow_constructible_v<second_base, Other>)
: first_base{std::forward<Arg>(arg)},
second_base{std::forward<Other>(other)} {}
/**
* @brief Constructs a pair by forwarding the arguments to its parts.
* @tparam Args Types of arguments to use to initialize the first element.
* @tparam Other Types of arguments to use to initialize the second element.
* @param args Arguments to use to initialize the first element.
* @param other Arguments to use to initialize the second element.
*/
template<typename... Args, typename... Other>
constexpr compressed_pair(std::piecewise_construct_t, std::tuple<Args...> args, std::tuple<Other...> other) noexcept(std::is_nothrow_constructible_v<first_base, Args...> && std::is_nothrow_constructible_v<second_base, Other...>)
: first_base{std::move(args), std::index_sequence_for<Args...>{}},
second_base{std::move(other), std::index_sequence_for<Other...>{}} {}
/*! @brief Default destructor. */
~compressed_pair() = default;
/**
* @brief Copy assignment operator.
* @param other The instance to copy from.
* @return This compressed pair object.
*/
constexpr compressed_pair &operator=(const compressed_pair &other) = default;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This compressed pair object.
*/
constexpr compressed_pair &operator=(compressed_pair &&other) noexcept = default;
/**
* @brief Returns the first element that a pair stores.
* @return The first element that a pair stores.
*/
[[nodiscard]] constexpr first_type &first() noexcept {
return static_cast<first_base &>(*this).get();
}
/*! @copydoc first */
[[nodiscard]] constexpr const first_type &first() const noexcept {
return static_cast<const first_base &>(*this).get();
}
/**
* @brief Returns the second element that a pair stores.
* @return The second element that a pair stores.
*/
[[nodiscard]] constexpr second_type &second() noexcept {
return static_cast<second_base &>(*this).get();
}
/*! @copydoc second */
[[nodiscard]] constexpr const second_type &second() const noexcept {
return static_cast<const second_base &>(*this).get();
}
/**
* @brief Swaps two compressed pair objects.
* @param other The compressed pair to swap with.
*/
constexpr void swap(compressed_pair &other) noexcept {
using std::swap;
swap(first(), other.first());
swap(second(), other.second());
}
/**
* @brief Extracts an element from the compressed pair.
* @tparam Index An integer value that is either 0 or 1.
* @return Returns a reference to the first element if `Index` is 0 and a
* reference to the second element if `Index` is 1.
*/
template<std::size_t Index>
[[nodiscard]] constexpr decltype(auto) get() noexcept {
if constexpr(Index == 0u) {
return first();
} else {
static_assert(Index == 1u, "Index out of bounds");
return second();
}
}
/*! @copydoc get */
template<std::size_t Index>
[[nodiscard]] constexpr decltype(auto) get() const noexcept {
if constexpr(Index == 0u) {
return first();
} else {
static_assert(Index == 1u, "Index out of bounds");
return second();
}
}
};
/**
* @brief Deduction guide.
* @tparam Type Type of value to use to initialize the first element.
* @tparam Other Type of value to use to initialize the second element.
*/
template<typename Type, typename Other>
compressed_pair(Type &&, Other &&) -> compressed_pair<std::decay_t<Type>, std::decay_t<Other>>;
/**
* @brief Swaps two compressed pair objects.
* @tparam First The type of the first element that the pairs store.
* @tparam Second The type of the second element that the pairs store.
* @param lhs A valid compressed pair object.
* @param rhs A valid compressed pair object.
*/
template<typename First, typename Second>
constexpr void swap(compressed_pair<First, Second> &lhs, compressed_pair<First, Second> &rhs) noexcept {
lhs.swap(rhs);
}
} // namespace entt
namespace std {
/**
* @brief `std::tuple_size` specialization for `compressed_pair`s.
* @tparam First The type of the first element that the pair stores.
* @tparam Second The type of the second element that the pair stores.
*/
template<typename First, typename Second>
struct tuple_size<entt::compressed_pair<First, Second>>: integral_constant<size_t, 2u> {};
/**
* @brief `std::tuple_element` specialization for `compressed_pair`s.
* @tparam Index The index of the type to return.
* @tparam First The type of the first element that the pair stores.
* @tparam Second The type of the second element that the pair stores.
*/
template<size_t Index, typename First, typename Second>
struct tuple_element<Index, entt::compressed_pair<First, Second>>: conditional<Index == 0u, First, Second> {
static_assert(Index < 2u, "Index out of bounds");
};
} // namespace std
#endif

View File

@@ -0,0 +1,97 @@
#ifndef ENTT_CORE_ENUM_HPP
#define ENTT_CORE_ENUM_HPP
#include <type_traits>
namespace entt {
/**
* @brief Enable bitmask support for enum classes.
* @tparam Type The enum type for which to enable bitmask support.
*/
template<typename Type, typename = void>
struct enum_as_bitmask: std::false_type {};
/*! @copydoc enum_as_bitmask */
template<typename Type>
struct enum_as_bitmask<Type, std::void_t<decltype(Type::_entt_enum_as_bitmask)>>: std::is_enum<Type> {};
/**
* @brief Helper variable template.
* @tparam Type The enum class type for which to enable bitmask support.
*/
template<typename Type>
inline constexpr bool enum_as_bitmask_v = enum_as_bitmask<Type>::value;
} // namespace entt
/**
* @brief Operator available for enums for which bitmask support is enabled.
* @tparam Type Enum class type.
* @param lhs The first value to use.
* @param rhs The second value to use.
* @return The result of invoking the operator on the underlying types of the
* two values provided.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator|(const Type lhs, const Type rhs) noexcept {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) | static_cast<std::underlying_type_t<Type>>(rhs));
}
/*! @copydoc operator| */
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator&(const Type lhs, const Type rhs) noexcept {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) & static_cast<std::underlying_type_t<Type>>(rhs));
}
/*! @copydoc operator| */
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator^(const Type lhs, const Type rhs) noexcept {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) ^ static_cast<std::underlying_type_t<Type>>(rhs));
}
/**
* @brief Operator available for enums for which bitmask support is enabled.
* @tparam Type Enum class type.
* @param value The value to use.
* @return The result of invoking the operator on the underlying types of the
* value provided.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator~(const Type value) noexcept {
return static_cast<Type>(~static_cast<std::underlying_type_t<Type>>(value));
}
/*! @copydoc operator~ */
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, bool>
operator!(const Type value) noexcept {
return !static_cast<std::underlying_type_t<Type>>(value);
}
/*! @copydoc operator| */
template<typename Type>
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator|=(Type &lhs, const Type rhs) noexcept {
return (lhs = (lhs | rhs));
}
/*! @copydoc operator| */
template<typename Type>
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator&=(Type &lhs, const Type rhs) noexcept {
return (lhs = (lhs & rhs));
}
/*! @copydoc operator| */
template<typename Type>
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator^=(Type &lhs, const Type rhs) noexcept {
return (lhs = (lhs ^ rhs));
}
#endif

View File

@@ -0,0 +1,33 @@
#ifndef ENTT_CORE_FAMILY_HPP
#define ENTT_CORE_FAMILY_HPP
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/**
* @brief Dynamic identifier generator.
*
* Utility class template that can be used to assign unique identifiers to types
* at runtime. Use different specializations to create separate sets of
* identifiers.
*/
template<typename...>
class family {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
inline static ENTT_MAYBE_ATOMIC(id_type) identifier{};
public:
/*! @brief Unsigned integer type. */
using value_type = id_type;
/*! @brief Statically generated unique identifier for the given type. */
template<typename... Type>
// at the time I'm writing, clang crashes during compilation if auto is used instead of family_type
inline static const value_type value = identifier++;
};
} // namespace entt
#endif

51
src/external/entt/src/entt/core/fwd.hpp vendored Normal file
View File

@@ -0,0 +1,51 @@
#ifndef ENTT_CORE_FWD_HPP
#define ENTT_CORE_FWD_HPP
#include <cstddef>
#include <cstdint>
#include "../config/config.h"
namespace entt {
/*! @brief Possible modes of an any object. */
enum class any_policy : std::uint8_t {
/*! @brief Default mode, the object does not own any elements. */
empty,
/*! @brief Owning mode, the object owns a dynamically allocated element. */
dynamic,
/*! @brief Owning mode, the object owns an embedded element. */
embedded,
/*! @brief Aliasing mode, the object _points_ to a non-const element. */
ref,
/*! @brief Const aliasing mode, the object _points_ to a const element. */
cref
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(double[2])>
class basic_any;
/*! @brief Alias declaration for type identifiers. */
using id_type = ENTT_ID_TYPE;
/*! @brief Alias declaration for the most common use case. */
using any = basic_any<>;
template<typename, typename>
class compressed_pair;
template<typename>
class basic_hashed_string;
/*! @brief Aliases for common character types. */
using hashed_string = basic_hashed_string<char>;
/*! @brief Aliases for common character types. */
using hashed_wstring = basic_hashed_string<wchar_t>;
// NOLINTNEXTLINE(bugprone-forward-declaration-namespace)
struct type_info;
} // namespace entt
#endif

View File

@@ -0,0 +1,311 @@
#ifndef ENTT_CORE_HASHED_STRING_HPP
#define ENTT_CORE_HASHED_STRING_HPP
#include <cstddef>
#include <cstdint>
#include <string_view>
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename = id_type>
struct fnv_1a_params;
template<>
struct fnv_1a_params<std::uint32_t> {
static constexpr auto offset = 2166136261;
static constexpr auto prime = 16777619;
};
template<>
struct fnv_1a_params<std::uint64_t> {
static constexpr auto offset = 14695981039346656037ull;
static constexpr auto prime = 1099511628211ull;
};
template<typename Char>
struct basic_hashed_string {
using value_type = Char;
using size_type = std::size_t;
using hash_type = id_type;
const value_type *repr;
size_type length;
hash_type hash;
};
} // namespace internal
/*! @endcond */
/**
* @brief Zero overhead unique identifier.
*
* A hashed string is a compile-time tool that allows users to use
* human-readable identifiers in the codebase while using their numeric
* counterparts at runtime.<br/>
* Because of that, a hashed string can also be used in constant expressions if
* required.
*
* @warning
* This class doesn't take ownership of user-supplied strings nor does it make a
* copy of them.
*
* @tparam Char Character type.
*/
template<typename Char>
class basic_hashed_string: internal::basic_hashed_string<Char> {
using base_type = internal::basic_hashed_string<Char>;
using params = internal::fnv_1a_params<>;
struct const_wrapper {
// non-explicit constructor on purpose
constexpr const_wrapper(const Char *str) noexcept
: repr{str} {}
const Char *repr;
};
// FowlerNollVo hash function v. 1a - the good
[[nodiscard]] static constexpr auto helper(const std::basic_string_view<Char> view) noexcept {
base_type base{view.data(), view.size(), params::offset};
for(auto &&curr: view) {
base.hash = (base.hash ^ static_cast<id_type>(curr)) * params::prime;
}
return base;
}
public:
/*! @brief Character type. */
using value_type = typename base_type::value_type;
/*! @brief Unsigned integer type. */
using size_type = typename base_type::size_type;
/*! @brief Unsigned integer type. */
using hash_type = typename base_type::hash_type;
/**
* @brief Returns directly the numeric representation of a string view.
* @param str Human-readable identifier.
* @param len Length of the string to hash.
* @return The numeric representation of the string.
*/
[[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) noexcept {
return basic_hashed_string{str, len};
}
/**
* @brief Returns directly the numeric representation of a string.
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifier.
* @return The numeric representation of the string.
*/
template<std::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
[[nodiscard]] static ENTT_CONSTEVAL hash_type value(const value_type (&str)[N]) noexcept {
return basic_hashed_string{str};
}
/**
* @brief Returns directly the numeric representation of a string.
* @param wrapper Helps achieving the purpose by relying on overloading.
* @return The numeric representation of the string.
*/
[[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) noexcept {
return basic_hashed_string{wrapper};
}
/*! @brief Constructs an empty hashed string. */
constexpr basic_hashed_string() noexcept
: basic_hashed_string{nullptr, 0u} {}
/**
* @brief Constructs a hashed string from a string view.
* @param str Human-readable identifier.
* @param len Length of the string to hash.
*/
constexpr basic_hashed_string(const value_type *str, const size_type len) noexcept
: base_type{helper({str, len})} {}
/**
* @brief Constructs a hashed string from an array of const characters.
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifier.
*/
template<std::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
ENTT_CONSTEVAL basic_hashed_string(const value_type (&str)[N]) noexcept
: base_type{helper({static_cast<const value_type *>(str)})} {}
/**
* @brief Explicit constructor on purpose to avoid constructing a hashed
* string directly from a `const value_type *`.
*
* @warning
* The lifetime of the string is not extended nor is it copied.
*
* @param wrapper Helps achieving the purpose by relying on overloading.
*/
explicit constexpr basic_hashed_string(const_wrapper wrapper) noexcept
: base_type{helper({wrapper.repr})} {}
/**
* @brief Returns the size a hashed string.
* @return The size of the hashed string.
*/
[[nodiscard]] constexpr size_type size() const noexcept {
return base_type::length;
}
/**
* @brief Returns the human-readable representation of a hashed string.
* @return The string used to initialize the hashed string.
*/
[[nodiscard]] constexpr const value_type *data() const noexcept {
return base_type::repr;
}
/**
* @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the hashed string.
*/
[[nodiscard]] constexpr hash_type value() const noexcept {
return base_type::hash;
}
/*! @copydoc data */
[[nodiscard]] constexpr operator const value_type *() const noexcept {
return data();
}
/**
* @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the hashed string.
*/
[[nodiscard]] constexpr operator hash_type() const noexcept {
return value();
}
};
/**
* @brief Deduction guide.
* @tparam Char Character type.
* @param str Human-readable identifier.
* @param len Length of the string to hash.
*/
template<typename Char>
basic_hashed_string(const Char *str, std::size_t len) -> basic_hashed_string<Char>;
/**
* @brief Deduction guide.
* @tparam Char Character type.
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifier.
*/
template<typename Char, std::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string<Char>;
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the two hashed strings are identical, false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator==(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
return lhs.value() == rhs.value();
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the two hashed strings differ, false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator!=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
return !(lhs == rhs);
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the first element is less than the second, false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator<(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
return lhs.value() < rhs.value();
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the first element is less than or equal to the second, false
* otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator<=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
return !(rhs < lhs);
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the first element is greater than the second, false
* otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator>(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
return rhs < lhs;
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the first element is greater than or equal to the second,
* false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator>=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
return !(lhs < rhs);
}
inline namespace literals {
/**
* @brief User defined literal for hashed strings.
* @param str The literal without its suffix.
* @return A properly initialized hashed string.
*/
[[nodiscard]] ENTT_CONSTEVAL hashed_string operator""_hs(const char *str, std::size_t) noexcept {
return hashed_string{str};
}
/**
* @brief User defined literal for hashed wstrings.
* @param str The literal without its suffix.
* @return A properly initialized hashed wstring.
*/
[[nodiscard]] ENTT_CONSTEVAL hashed_wstring operator""_hws(const wchar_t *str, std::size_t) noexcept {
return hashed_wstring{str};
}
} // namespace literals
} // namespace entt
#endif

View File

@@ -0,0 +1,35 @@
#ifndef ENTT_CORE_IDENT_HPP
#define ENTT_CORE_IDENT_HPP
#include <cstddef>
#include <type_traits>
#include <utility>
#include "fwd.hpp"
#include "type_traits.hpp"
namespace entt {
/**
* @brief Type integral identifiers.
* @tparam Type List of types for which to generate identifiers.
*/
template<typename... Type>
class ident {
template<typename Curr, std::size_t... Index>
[[nodiscard]] static constexpr id_type get(std::index_sequence<Index...>) noexcept {
static_assert((std::is_same_v<Curr, Type> || ...), "Invalid type");
return (0 + ... + (std::is_same_v<Curr, type_list_element_t<Index, type_list<std::decay_t<Type>...>>> ? id_type{Index} : id_type{}));
}
public:
/*! @brief Unsigned integer type. */
using value_type = id_type;
/*! @brief Statically generated unique identifier for the given type. */
template<typename Curr>
static constexpr value_type value = get<std::decay_t<Curr>>(std::index_sequence_for<Type...>{});
};
} // namespace entt
#endif

View File

@@ -0,0 +1,197 @@
#ifndef ENTT_CORE_ITERATOR_HPP
#define ENTT_CORE_ITERATOR_HPP
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
namespace entt {
/**
* @brief Helper type to use as pointer with input iterators.
* @tparam Type of wrapped value.
*/
template<typename Type>
struct input_iterator_pointer final {
/*! @brief Value type. */
using value_type = Type;
/*! @brief Pointer type. */
using pointer = Type *;
/*! @brief Reference type. */
using reference = Type &;
/**
* @brief Constructs a proxy object by move.
* @param val Value to use to initialize the proxy object.
*/
constexpr input_iterator_pointer(value_type &&val) noexcept(std::is_nothrow_move_constructible_v<value_type>)
: value{std::move(val)} {}
/**
* @brief Access operator for accessing wrapped values.
* @return A pointer to the wrapped value.
*/
[[nodiscard]] constexpr pointer operator->() noexcept {
return std::addressof(value);
}
/**
* @brief Dereference operator for accessing wrapped values.
* @return A reference to the wrapped value.
*/
[[nodiscard]] constexpr reference operator*() noexcept {
return value;
}
private:
Type value;
};
/**
* @brief Plain iota iterator (waiting for C++20).
* @tparam Type Value type.
*/
template<typename Type>
class iota_iterator final {
static_assert(std::is_integral_v<Type>, "Not an integral type");
public:
/*! @brief Value type, likely an integral one. */
using value_type = Type;
/*! @brief Invalid pointer type. */
using pointer = void;
/*! @brief Non-reference type, same as value type. */
using reference = value_type;
/*! @brief Difference type. */
using difference_type = std::ptrdiff_t;
/*! @brief Iterator category. */
using iterator_category = std::input_iterator_tag;
/*! @brief Default constructor. */
constexpr iota_iterator() noexcept
: current{} {}
/**
* @brief Constructs an iota iterator from a given value.
* @param init The initial value assigned to the iota iterator.
*/
constexpr iota_iterator(const value_type init) noexcept
: current{init} {}
/**
* @brief Pre-increment operator.
* @return This iota iterator.
*/
constexpr iota_iterator &operator++() noexcept {
return ++current, *this;
}
/**
* @brief Post-increment operator.
* @return This iota iterator.
*/
constexpr iota_iterator operator++(int) noexcept {
const iota_iterator orig = *this;
return ++(*this), orig;
}
/**
* @brief Dereference operator.
* @return The underlying value.
*/
[[nodiscard]] constexpr reference operator*() const noexcept {
return current;
}
private:
value_type current;
};
/**
* @brief Comparison operator.
* @tparam Type Value type of the iota iterator.
* @param lhs A properly initialized iota iterator.
* @param rhs A properly initialized iota iterator.
* @return True if the two iterators are identical, false otherwise.
*/
template<typename Type>
[[nodiscard]] constexpr bool operator==(const iota_iterator<Type> &lhs, const iota_iterator<Type> &rhs) noexcept {
return *lhs == *rhs;
}
/**
* @brief Comparison operator.
* @tparam Type Value type of the iota iterator.
* @param lhs A properly initialized iota iterator.
* @param rhs A properly initialized iota iterator.
* @return True if the two iterators differ, false otherwise.
*/
template<typename Type>
[[nodiscard]] constexpr bool operator!=(const iota_iterator<Type> &lhs, const iota_iterator<Type> &rhs) noexcept {
return !(lhs == rhs);
}
/**
* @brief Utility class to create an iterable object from a pair of iterators.
* @tparam It Type of iterator.
* @tparam Sentinel Type of sentinel.
*/
template<typename It, typename Sentinel = It>
struct iterable_adaptor final {
/*! @brief Value type. */
using value_type = typename std::iterator_traits<It>::value_type;
/*! @brief Iterator type. */
using iterator = It;
/*! @brief Sentinel type. */
using sentinel = Sentinel;
/*! @brief Default constructor. */
constexpr iterable_adaptor() noexcept(std::is_nothrow_default_constructible_v<iterator> && std::is_nothrow_default_constructible_v<sentinel>)
: first{},
last{} {}
/**
* @brief Creates an iterable object from a pair of iterators.
* @param from Begin iterator.
* @param to End iterator.
*/
constexpr iterable_adaptor(iterator from, sentinel to) noexcept(std::is_nothrow_move_constructible_v<iterator> && std::is_nothrow_move_constructible_v<sentinel>)
: first{std::move(from)},
last{std::move(to)} {}
/**
* @brief Returns an iterator to the beginning.
* @return An iterator to the first element of the range.
*/
[[nodiscard]] constexpr iterator begin() const noexcept {
return first;
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last element of the
* range.
*/
[[nodiscard]] constexpr sentinel end() const noexcept {
return last;
}
/*! @copydoc begin */
[[nodiscard]] constexpr iterator cbegin() const noexcept {
return begin();
}
/*! @copydoc end */
[[nodiscard]] constexpr sentinel cend() const noexcept {
return end();
}
private:
It first;
Sentinel last;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,244 @@
#ifndef ENTT_CORE_MEMORY_HPP
#define ENTT_CORE_MEMORY_HPP
#include <cstddef>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
namespace entt {
/**
* @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20).
* @tparam Type Pointer type.
* @param ptr Fancy or raw pointer.
* @return A raw pointer that represents the address of the original pointer.
*/
template<typename Type>
[[nodiscard]] constexpr auto to_address(Type &&ptr) noexcept {
if constexpr(std::is_pointer_v<std::decay_t<Type>>) {
return ptr;
} else {
return to_address(std::forward<Type>(ptr).operator->());
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value) {
lhs = rhs;
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value) {
lhs = std::move(rhs);
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_swap::value) {
using std::swap;
swap(lhs, rhs);
} else {
ENTT_ASSERT_CONSTEXPR(lhs == rhs, "Cannot swap the containers");
}
}
/**
* @brief Deleter for allocator-aware unique pointers (waiting for C++20).
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Allocator>
struct allocation_deleter: private Allocator {
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Pointer type. */
using pointer = typename std::allocator_traits<Allocator>::pointer;
/**
* @brief Inherited constructors.
* @param alloc The allocator to use.
*/
constexpr allocation_deleter(const allocator_type &alloc) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
: Allocator{alloc} {}
/**
* @brief Destroys the pointed object and deallocates its memory.
* @param ptr A valid pointer to an object of the given type.
*/
constexpr void operator()(pointer ptr) noexcept(std::is_nothrow_destructible_v<typename allocator_type::value_type>) {
using alloc_traits = std::allocator_traits<Allocator>;
alloc_traits::destroy(*this, to_address(ptr));
alloc_traits::deallocate(*this, ptr, 1u);
}
};
/**
* @brief Allows `std::unique_ptr` to use allocators (waiting for C++20).
* @tparam Type Type of object to allocate for and to construct.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return A properly initialized unique pointer with a custom deleter.
*/
template<typename Type, typename Allocator, typename... Args>
ENTT_CONSTEXPR auto allocate_unique(Allocator &allocator, Args &&...args) {
static_assert(!std::is_array_v<Type>, "Array types are not supported");
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
using allocator_type = typename alloc_traits::allocator_type;
allocator_type alloc{allocator};
auto ptr = alloc_traits::allocate(alloc, 1u);
ENTT_TRY {
alloc_traits::construct(alloc, to_address(ptr), std::forward<Args>(args)...);
}
ENTT_CATCH {
alloc_traits::deallocate(alloc, ptr, 1u);
ENTT_THROW;
}
return std::unique_ptr<Type, allocation_deleter<allocator_type>>{ptr, alloc};
}
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Type>
struct uses_allocator_construction {
template<typename Allocator, typename... Params>
static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) noexcept {
if constexpr(!std::uses_allocator_v<Type, Allocator> && std::is_constructible_v<Type, Params...>) {
return std::forward_as_tuple(std::forward<Params>(params)...);
} else {
static_assert(std::uses_allocator_v<Type, Allocator>, "Ill-formed request");
if constexpr(std::is_constructible_v<Type, std::allocator_arg_t, const Allocator &, Params...>) {
return std::tuple<std::allocator_arg_t, const Allocator &, Params &&...>{std::allocator_arg, allocator, std::forward<Params>(params)...};
} else {
static_assert(std::is_constructible_v<Type, Params..., const Allocator &>, "Ill-formed request");
return std::forward_as_tuple(std::forward<Params>(params)..., allocator);
}
}
}
};
template<typename Type, typename Other>
struct uses_allocator_construction<std::pair<Type, Other>> {
using type = std::pair<Type, Other>;
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) noexcept {
return std::make_tuple(
std::piecewise_construct,
std::apply([&allocator](auto &&...curr) { return uses_allocator_construction<Type>::args(allocator, std::forward<decltype(curr)>(curr)...); }, std::forward<First>(first)),
std::apply([&allocator](auto &&...curr) { return uses_allocator_construction<Other>::args(allocator, std::forward<decltype(curr)>(curr)...); }, std::forward<Second>(second)));
}
template<typename Allocator>
static constexpr auto args(const Allocator &allocator) noexcept {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{});
}
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) noexcept {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward<First>(first)), std::forward_as_tuple(std::forward<Second>(second)));
}
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, const std::pair<First, Second> &value) noexcept {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second));
}
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, std::pair<First, Second> &&value) noexcept {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second)));
}
};
} // namespace internal
/*! @endcond */
/**
* @brief Uses-allocator construction utility (waiting for C++20).
*
* Primarily intended for internal use. Prepares the argument list needed to
* create an object of a given type by means of uses-allocator construction.
*
* @tparam Type Type to return arguments for.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return The arguments needed to create an object of the given type.
*/
template<typename Type, typename Allocator, typename... Args>
constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) noexcept {
return internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...);
}
/**
* @brief Uses-allocator construction utility (waiting for C++20).
*
* Primarily intended for internal use. Creates an object of a given type by
* means of uses-allocator construction.
*
* @tparam Type Type of object to create.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return A newly created object of the given type.
*/
template<typename Type, typename Allocator, typename... Args>
constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) {
return std::make_from_tuple<Type>(internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...));
}
/**
* @brief Uses-allocator construction utility (waiting for C++20).
*
* Primarily intended for internal use. Creates an object of a given type by
* means of uses-allocator construction at an uninitialized memory location.
*
* @tparam Type Type of object to create.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the object.
* @param value Memory location in which to place the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return A pointer to the newly created object of the given type.
*/
template<typename Type, typename Allocator, typename... Args>
constexpr Type *uninitialized_construct_using_allocator(Type *value, const Allocator &allocator, Args &&...args) {
return std::apply([value](auto &&...curr) { return ::new(value) Type(std::forward<decltype(curr)>(curr)...); }, internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...));
}
} // namespace entt
#endif

View File

@@ -0,0 +1,60 @@
#ifndef ENTT_CORE_MONOSTATE_HPP
#define ENTT_CORE_MONOSTATE_HPP
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/**
* @brief Minimal implementation of the monostate pattern.
*
* A minimal, yet complete configuration system built on top of the monostate
* pattern. Thread safe by design, it works only with basic types like `int`s or
* `bool`s.<br/>
* Multiple types and therefore more than one value can be associated with a
* single key. Because of this, users must pay attention to use the same type
* both during an assignment and when they try to read back their data.
* Otherwise, they can incur in unexpected results.
*/
template<id_type>
struct monostate {
/**
* @brief Assigns a value of a specific type to a given key.
* @tparam Type Type of the value to assign.
* @param val User data to assign to the given key.
* @return This monostate object.
*/
template<typename Type>
monostate &operator=(Type val) noexcept {
value<Type> = val;
return *this;
}
/**
* @brief Gets a value of a specific type for a given key.
* @tparam Type Type of the value to get.
* @return Stored value, if any.
*/
template<typename Type>
operator Type() const noexcept {
return value<Type>;
}
private:
template<typename Type>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
inline static ENTT_MAYBE_ATOMIC(Type) value{};
};
/**
* @brief Helper variable template.
* @tparam Value Value used to differentiate between different variables.
*/
template<id_type Value>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
inline monostate<Value> monostate_v{};
} // namespace entt
#endif

View File

@@ -0,0 +1,20 @@
#ifndef ENTT_CORE_RANGES_HPP
#define ENTT_CORE_RANGES_HPP
#if __has_include(<version>)
# include <version>
#
# if defined(__cpp_lib_ranges)
# include <ranges>
# include "iterator.hpp"
template<class... Args>
inline constexpr bool std::ranges::enable_borrowed_range<entt::iterable_adaptor<Args...>>{true};
template<class... Args>
inline constexpr bool std::ranges::enable_view<entt::iterable_adaptor<Args...>>{true};
# endif
#endif
#endif

View File

@@ -0,0 +1,90 @@
#ifndef ENTT_CORE_TUPLE_HPP
#define ENTT_CORE_TUPLE_HPP
#include <tuple>
#include <type_traits>
#include <utility>
namespace entt {
/**
* @brief Provides the member constant `value` to true if a given type is a
* tuple, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type>
struct is_tuple: std::false_type {};
/**
* @copybrief is_tuple
* @tparam Args Tuple template arguments.
*/
template<typename... Args>
struct is_tuple<std::tuple<Args...>>: std::true_type {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_tuple_v = is_tuple<Type>::value;
/**
* @brief Utility function to unwrap tuples of a single element.
* @tparam Type Tuple type of any sizes.
* @param value A tuple object of the given type.
* @return The tuple itself if it contains more than one element, the first
* element otherwise.
*/
template<typename Type>
constexpr decltype(auto) unwrap_tuple(Type &&value) noexcept {
if constexpr(std::tuple_size_v<std::remove_reference_t<Type>> == 1u) {
return std::get<0>(std::forward<Type>(value));
} else {
return std::forward<Type>(value);
}
}
/**
* @brief Utility class to forward-and-apply tuple objects.
* @tparam Func Type of underlying invocable object.
*/
template<typename Func>
struct forward_apply: private Func {
/**
* @brief Constructs a forward-and-apply object.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<typename... Args>
constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v<Func, Args...>)
: Func{std::forward<Args>(args)...} {}
/**
* @brief Forwards and applies the arguments with the underlying function.
* @tparam Type Tuple-like type to forward to the underlying function.
* @param args Parameters to forward to the underlying function.
* @return Return value of the underlying function, if any.
*/
template<typename Type>
constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(std::apply(std::declval<Func &>(), args))) {
return std::apply(static_cast<Func &>(*this), std::forward<Type>(args));
}
/*! @copydoc operator()() */
template<typename Type>
constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(std::apply(std::declval<const Func &>(), args))) {
return std::apply(static_cast<const Func &>(*this), std::forward<Type>(args));
}
};
/**
* @brief Deduction guide.
* @tparam Func Type of underlying invocable object.
*/
template<typename Func>
forward_apply(Func) -> forward_apply<std::remove_reference_t<std::remove_cv_t<Func>>>;
} // namespace entt
#endif

View File

@@ -0,0 +1,269 @@
#ifndef ENTT_CORE_TYPE_INFO_HPP
#define ENTT_CORE_TYPE_INFO_HPP
#include <string_view>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/attribute.h"
#include "fwd.hpp"
#include "hashed_string.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
struct ENTT_API type_index final {
[[nodiscard]] static id_type next() noexcept {
static ENTT_MAYBE_ATOMIC(id_type) value{};
return value++;
}
};
template<typename Type>
[[nodiscard]] constexpr auto stripped_type_name() noexcept {
#if defined ENTT_PRETTY_FUNCTION
const std::string_view pretty_function{static_cast<const char *>(ENTT_PRETTY_FUNCTION)};
auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1);
auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first);
return value;
#else
return std::string_view{""};
#endif
}
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] constexpr std::string_view type_name(int) noexcept {
constexpr auto value = stripped_type_name<Type>();
return value;
}
template<typename Type>
[[nodiscard]] std::string_view type_name(char) noexcept {
static const auto value = stripped_type_name<Type>();
return value;
}
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] constexpr id_type type_hash(int) noexcept {
constexpr auto stripped = stripped_type_name<Type>();
constexpr auto value = hashed_string::value(stripped.data(), stripped.size());
return value;
}
template<typename Type>
[[nodiscard]] id_type type_hash(char) noexcept {
static const auto value = [](const auto stripped) {
return hashed_string::value(stripped.data(), stripped.size());
}(stripped_type_name<Type>());
return value;
}
} // namespace internal
/*! @endcond */
/**
* @brief Type sequential identifier.
* @tparam Type Type for which to generate a sequential identifier.
*/
template<typename Type, typename = void>
struct ENTT_API type_index final {
/**
* @brief Returns the sequential identifier of a given type.
* @return The sequential identifier of a given type.
*/
[[nodiscard]] static id_type value() noexcept {
static const id_type value = internal::type_index::next();
return value;
}
/*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const noexcept {
return value();
}
};
/**
* @brief Type hash.
* @tparam Type Type for which to generate a hash value.
*/
template<typename Type, typename = void>
struct type_hash final {
/**
* @brief Returns the numeric representation of a given type.
* @return The numeric representation of the given type.
*/
#if defined ENTT_PRETTY_FUNCTION
[[nodiscard]] static constexpr id_type value() noexcept {
return internal::type_hash<Type>(0);
#else
[[nodiscard]] static constexpr id_type value() noexcept {
return type_index<Type>::value();
#endif
}
/*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const noexcept {
return value();
}
};
/**
* @brief Type name.
* @tparam Type Type for which to generate a name.
*/
template<typename Type, typename = void>
struct type_name final {
/**
* @brief Returns the name of a given type.
* @return The name of the given type.
*/
[[nodiscard]] static constexpr std::string_view value() noexcept {
return internal::type_name<Type>(0);
}
/*! @copydoc value */
[[nodiscard]] constexpr operator std::string_view() const noexcept {
return value();
}
};
/*! @brief Implementation specific information about a type. */
struct type_info final {
/**
* @brief Constructs a type info object for a given type.
* @tparam Type Type for which to construct a type info object.
*/
template<typename Type>
// NOLINTBEGIN(modernize-use-transparent-functors)
constexpr type_info(std::in_place_type_t<Type>) noexcept
: seq{type_index<std::remove_cv_t<std::remove_reference_t<Type>>>::value()},
identifier{type_hash<std::remove_cv_t<std::remove_reference_t<Type>>>::value()},
alias{type_name<std::remove_cv_t<std::remove_reference_t<Type>>>::value()} {}
// NOLINTEND(modernize-use-transparent-functors)
/**
* @brief Type index.
* @return Type index.
*/
[[nodiscard]] constexpr id_type index() const noexcept {
return seq;
}
/**
* @brief Type hash.
* @return Type hash.
*/
[[nodiscard]] constexpr id_type hash() const noexcept {
return identifier;
}
/**
* @brief Type name.
* @return Type name.
*/
[[nodiscard]] constexpr std::string_view name() const noexcept {
return alias;
}
private:
id_type seq;
id_type identifier;
std::string_view alias;
};
/**
* @brief Compares the contents of two type info objects.
* @param lhs A type info object.
* @param rhs A type info object.
* @return True if the two type info objects are identical, false otherwise.
*/
[[nodiscard]] constexpr bool operator==(const type_info &lhs, const type_info &rhs) noexcept {
return lhs.hash() == rhs.hash();
}
/**
* @brief Compares the contents of two type info objects.
* @param lhs A type info object.
* @param rhs A type info object.
* @return True if the two type info objects differ, false otherwise.
*/
[[nodiscard]] constexpr bool operator!=(const type_info &lhs, const type_info &rhs) noexcept {
return !(lhs == rhs);
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is less than the second, false otherwise.
*/
[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) noexcept {
return lhs.index() < rhs.index();
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is less than or equal to the second, false
* otherwise.
*/
[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) noexcept {
return !(rhs < lhs);
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is greater than the second, false
* otherwise.
*/
[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) noexcept {
return rhs < lhs;
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is greater than or equal to the second,
* false otherwise.
*/
[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) noexcept {
return !(lhs < rhs);
}
/**
* @brief Returns the type info object associated to a given type.
*
* The returned element refers to an object with static storage duration.<br/>
* The type doesn't need to be a complete type. If the type is a reference, the
* result refers to the referenced type. In all cases, top-level cv-qualifiers
* are ignored.
*
* @tparam Type Type for which to generate a type info object.
* @return A reference to a properly initialized type info object.
*/
template<typename Type>
[[nodiscard]] const type_info &type_id() noexcept {
if constexpr(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>) {
static const type_info instance{std::in_place_type<Type>};
return instance;
} else {
return type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
}
}
/*! @copydoc type_id */
template<typename Type>
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
[[nodiscard]] const type_info &type_id(Type &&) noexcept {
return type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
}
} // namespace entt
#endif

View File

@@ -0,0 +1,921 @@
#ifndef ENTT_CORE_TYPE_TRAITS_HPP
#define ENTT_CORE_TYPE_TRAITS_HPP
#include <cstddef>
#include <iterator>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/**
* @brief Utility class to disambiguate overloaded functions.
* @tparam N Number of choices available.
*/
template<std::size_t N>
struct choice_t
// unfortunately, doxygen cannot parse such a construct
: /*! @cond TURN_OFF_DOXYGEN */ choice_t<N - 1> /*! @endcond */
{};
/*! @copybrief choice_t */
template<>
struct choice_t<0> {};
/**
* @brief Variable template for the choice trick.
* @tparam N Number of choices available.
*/
template<std::size_t N>
inline constexpr choice_t<N> choice{};
/**
* @brief Identity type trait.
*
* Useful to establish non-deduced contexts in template argument deduction
* (waiting for C++20) or to provide types through function arguments.
*
* @tparam Type A type.
*/
template<typename Type>
struct type_identity {
/*! @brief Identity type. */
using type = Type;
};
/**
* @brief Helper type.
* @tparam Type A type.
*/
template<typename Type>
using type_identity_t = typename type_identity<Type>::type;
/**
* @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains.
* @tparam Type The type of which to return the size.
*/
template<typename Type, typename = void>
struct size_of: std::integral_constant<std::size_t, 0u> {};
/*! @copydoc size_of */
template<typename Type>
struct size_of<Type, std::void_t<decltype(sizeof(Type))>>
// NOLINTNEXTLINE(bugprone-sizeof-expression)
: std::integral_constant<std::size_t, sizeof(Type)> {};
/**
* @brief Helper variable template.
* @tparam Type The type of which to return the size.
*/
template<typename Type>
inline constexpr std::size_t size_of_v = size_of<Type>::value;
/**
* @brief Using declaration to be used to _repeat_ the same type a number of
* times equal to the size of a given parameter pack.
* @tparam Type A type to repeat.
*/
template<typename Type, typename>
using unpack_as_type = Type;
/**
* @brief Helper variable template to be used to _repeat_ the same value a
* number of times equal to the size of a given parameter pack.
* @tparam Value A value to repeat.
*/
template<auto Value, typename>
inline constexpr auto unpack_as_value = Value;
/**
* @brief Wraps a static constant.
* @tparam Value A static constant.
*/
template<auto Value>
using integral_constant = std::integral_constant<decltype(Value), Value>;
/**
* @brief Alias template to facilitate the creation of named values.
* @tparam Value A constant value at least convertible to `id_type`.
*/
template<id_type Value>
using tag = integral_constant<Value>;
/**
* @brief A class to use to push around lists of types, nothing more.
* @tparam Type Types provided by the type list.
*/
template<typename... Type>
struct type_list {
/*! @brief Type list type. */
using type = type_list;
/*! @brief Compile-time number of elements in the type list. */
static constexpr auto size = sizeof...(Type);
};
/*! @brief Primary template isn't defined on purpose. */
template<std::size_t, typename>
struct type_list_element;
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam Index Index of the type to return.
* @tparam First First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<std::size_t Index, typename First, typename... Other>
struct type_list_element<Index, type_list<First, Other...>>
: type_list_element<Index - 1u, type_list<Other...>> {};
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam First First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<typename First, typename... Other>
struct type_list_element<0u, type_list<First, Other...>> {
/*! @brief Searched type. */
using type = First;
};
/**
* @brief Helper type.
* @tparam Index Index of the type to return.
* @tparam List Type list to search into.
*/
template<std::size_t Index, typename List>
using type_list_element_t = typename type_list_element<Index, List>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename, typename>
struct type_list_index;
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
* @tparam First First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<typename Type, typename First, typename... Other>
struct type_list_index<Type, type_list<First, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 1u + type_list_index<Type, type_list<Other...>>::value;
};
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
* @tparam Other Other types provided by the type list.
*/
template<typename Type, typename... Other>
struct type_list_index<Type, type_list<Type, Other...>> {
static_assert(type_list_index<Type, type_list<Other...>>::value == sizeof...(Other), "Non-unique type");
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
*/
template<typename Type>
struct type_list_index<Type, type_list<>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Helper variable template.
* @tparam List Type list.
* @tparam Type Type to look for and for which to return the index.
*/
template<typename Type, typename List>
inline constexpr std::size_t type_list_index_v = type_list_index<Type, List>::value;
/**
* @brief Concatenates multiple type lists.
* @tparam Type Types provided by the first type list.
* @tparam Other Types provided by the second type list.
* @return A type list composed by the types of both the type lists.
*/
template<typename... Type, typename... Other>
constexpr type_list<Type..., Other...> operator+(type_list<Type...>, type_list<Other...>) {
return {};
}
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct type_list_cat;
/*! @brief Concatenates multiple type lists. */
template<>
struct type_list_cat<> {
/*! @brief A type list composed by the types of all the type lists. */
using type = type_list<>;
};
/**
* @brief Concatenates multiple type lists.
* @tparam Type Types provided by the first type list.
* @tparam Other Types provided by the second type list.
* @tparam List Other type lists, if any.
*/
template<typename... Type, typename... Other, typename... List>
struct type_list_cat<type_list<Type...>, type_list<Other...>, List...> {
/*! @brief A type list composed by the types of all the type lists. */
using type = typename type_list_cat<type_list<Type..., Other...>, List...>::type;
};
/**
* @brief Concatenates multiple type lists.
* @tparam Type Types provided by the type list.
*/
template<typename... Type>
struct type_list_cat<type_list<Type...>> {
/*! @brief A type list composed by the types of all the type lists. */
using type = type_list<Type...>;
};
/**
* @brief Helper type.
* @tparam List Type lists to concatenate.
*/
template<typename... List>
using type_list_cat_t = typename type_list_cat<List...>::type;
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename...>
struct type_list_unique;
template<typename First, typename... Other, typename... Type>
struct type_list_unique<type_list<First, Other...>, Type...>
: std::conditional_t<(std::is_same_v<First, Type> || ...), type_list_unique<type_list<Other...>, Type...>, type_list_unique<type_list<Other...>, Type..., First>> {};
template<typename... Type>
struct type_list_unique<type_list<>, Type...> {
using type = type_list<Type...>;
};
} // namespace internal
/*! @endcond */
/**
* @brief Removes duplicates types from a type list.
* @tparam List Type list.
*/
template<typename List>
struct type_list_unique {
/*! @brief A type list without duplicate types. */
using type = typename internal::type_list_unique<List>::type;
};
/**
* @brief Helper type.
* @tparam List Type list.
*/
template<typename List>
using type_list_unique_t = typename type_list_unique<List>::type;
/**
* @brief Provides the member constant `value` to true if a type list contains a
* given type, false otherwise.
* @tparam List Type list.
* @tparam Type Type to look for.
*/
template<typename List, typename Type>
struct type_list_contains;
/**
* @copybrief type_list_contains
* @tparam Type Types provided by the type list.
* @tparam Other Type to look for.
*/
template<typename... Type, typename Other>
struct type_list_contains<type_list<Type...>, Other>
: std::bool_constant<(std::is_same_v<Type, Other> || ...)> {};
/**
* @brief Helper variable template.
* @tparam List Type list.
* @tparam Type Type to look for.
*/
template<typename List, typename Type>
inline constexpr bool type_list_contains_v = type_list_contains<List, Type>::value;
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct type_list_diff;
/**
* @brief Computes the difference between two type lists.
* @tparam Type Types provided by the first type list.
* @tparam Other Types provided by the second type list.
*/
template<typename... Type, typename... Other>
struct type_list_diff<type_list<Type...>, type_list<Other...>> {
/*! @brief A type list that is the difference between the two type lists. */
using type = type_list_cat_t<std::conditional_t<type_list_contains_v<type_list<Other...>, Type>, type_list<>, type_list<Type>>...>;
};
/**
* @brief Helper type.
* @tparam List Type lists between which to compute the difference.
*/
template<typename... List>
using type_list_diff_t = typename type_list_diff<List...>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename, template<typename...> class>
struct type_list_transform;
/**
* @brief Applies a given _function_ to a type list and generate a new list.
* @tparam Type Types provided by the type list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<type_list<Type...>, Op> {
/*! @brief Resulting type list after applying the transform function. */
// NOLINTNEXTLINE(modernize-type-traits)
using type = type_list<typename Op<Type>::type...>;
};
/**
* @brief Helper type.
* @tparam List Type list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename List, template<typename...> class Op>
using type_list_transform_t = typename type_list_transform<List, Op>::type;
/**
* @brief A class to use to push around lists of constant values, nothing more.
* @tparam Value Values provided by the value list.
*/
template<auto... Value>
struct value_list {
/*! @brief Value list type. */
using type = value_list;
/*! @brief Compile-time number of elements in the value list. */
static constexpr auto size = sizeof...(Value);
};
/*! @brief Primary template isn't defined on purpose. */
template<std::size_t, typename>
struct value_list_element;
/**
* @brief Provides compile-time indexed access to the values of a value list.
* @tparam Index Index of the value to return.
* @tparam Value First value provided by the value list.
* @tparam Other Other values provided by the value list.
*/
template<std::size_t Index, auto Value, auto... Other>
struct value_list_element<Index, value_list<Value, Other...>>
: value_list_element<Index - 1u, value_list<Other...>> {};
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam Value First value provided by the value list.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto... Other>
struct value_list_element<0u, value_list<Value, Other...>> {
/*! @brief Searched type. */
using type = decltype(Value);
/*! @brief Searched value. */
static constexpr auto value = Value;
};
/**
* @brief Helper type.
* @tparam Index Index of the type to return.
* @tparam List Value list to search into.
*/
template<std::size_t Index, typename List>
using value_list_element_t = typename value_list_element<Index, List>::type;
/**
* @brief Helper type.
* @tparam Index Index of the value to return.
* @tparam List Value list to search into.
*/
template<std::size_t Index, typename List>
inline constexpr auto value_list_element_v = value_list_element<Index, List>::value;
/*! @brief Primary template isn't defined on purpose. */
template<auto, typename>
struct value_list_index;
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam First First value provided by the value list.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto First, auto... Other>
struct value_list_index<Value, value_list<First, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 1u + value_list_index<Value, value_list<Other...>>::value;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto... Other>
struct value_list_index<Value, value_list<Value, Other...>> {
static_assert(value_list_index<Value, value_list<Other...>>::value == sizeof...(Other), "Non-unique type");
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value>
struct value_list_index<Value, value_list<>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Helper variable template.
* @tparam List Value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value, typename List>
inline constexpr std::size_t value_list_index_v = value_list_index<Value, List>::value;
/**
* @brief Concatenates multiple value lists.
* @tparam Value Values provided by the first value list.
* @tparam Other Values provided by the second value list.
* @return A value list composed by the values of both the value lists.
*/
template<auto... Value, auto... Other>
constexpr value_list<Value..., Other...> operator+(value_list<Value...>, value_list<Other...>) {
return {};
}
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct value_list_cat;
/*! @brief Concatenates multiple value lists. */
template<>
struct value_list_cat<> {
/*! @brief A value list composed by the values of all the value lists. */
using type = value_list<>;
};
/**
* @brief Concatenates multiple value lists.
* @tparam Value Values provided by the first value list.
* @tparam Other Values provided by the second value list.
* @tparam List Other value lists, if any.
*/
template<auto... Value, auto... Other, typename... List>
struct value_list_cat<value_list<Value...>, value_list<Other...>, List...> {
/*! @brief A value list composed by the values of all the value lists. */
using type = typename value_list_cat<value_list<Value..., Other...>, List...>::type;
};
/**
* @brief Concatenates multiple value lists.
* @tparam Value Values provided by the value list.
*/
template<auto... Value>
struct value_list_cat<value_list<Value...>> {
/*! @brief A value list composed by the values of all the value lists. */
using type = value_list<Value...>;
};
/**
* @brief Helper type.
* @tparam List Value lists to concatenate.
*/
template<typename... List>
using value_list_cat_t = typename value_list_cat<List...>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename>
struct value_list_unique;
/**
* @brief Removes duplicates values from a value list.
* @tparam Value One of the values provided by the given value list.
* @tparam Other The other values provided by the given value list.
*/
template<auto Value, auto... Other>
struct value_list_unique<value_list<Value, Other...>> {
/*! @brief A value list without duplicate types. */
using type = std::conditional_t<
((Value == Other) || ...),
typename value_list_unique<value_list<Other...>>::type,
value_list_cat_t<value_list<Value>, typename value_list_unique<value_list<Other...>>::type>>;
};
/*! @brief Removes duplicates values from a value list. */
template<>
struct value_list_unique<value_list<>> {
/*! @brief A value list without duplicate types. */
using type = value_list<>;
};
/**
* @brief Helper type.
* @tparam Type A value list.
*/
template<typename Type>
using value_list_unique_t = typename value_list_unique<Type>::type;
/**
* @brief Provides the member constant `value` to true if a value list contains
* a given value, false otherwise.
* @tparam List Value list.
* @tparam Value Value to look for.
*/
template<typename List, auto Value>
struct value_list_contains;
/**
* @copybrief value_list_contains
* @tparam Value Values provided by the value list.
* @tparam Other Value to look for.
*/
template<auto... Value, auto Other>
struct value_list_contains<value_list<Value...>, Other>
: std::bool_constant<((Value == Other) || ...)> {};
/**
* @brief Helper variable template.
* @tparam List Value list.
* @tparam Value Value to look for.
*/
template<typename List, auto Value>
inline constexpr bool value_list_contains_v = value_list_contains<List, Value>::value;
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct value_list_diff;
/**
* @brief Computes the difference between two value lists.
* @tparam Value Values provided by the first value list.
* @tparam Other Values provided by the second value list.
*/
template<auto... Value, auto... Other>
struct value_list_diff<value_list<Value...>, value_list<Other...>> {
/*! @brief A value list that is the difference between the two value lists. */
using type = value_list_cat_t<std::conditional_t<value_list_contains_v<value_list<Other...>, Value>, value_list<>, value_list<Value>>...>;
};
/**
* @brief Helper type.
* @tparam List Value lists between which to compute the difference.
*/
template<typename... List>
using value_list_diff_t = typename value_list_diff<List...>::type;
/*! @brief Same as std::is_invocable, but with tuples. */
template<typename, typename>
struct is_applicable: std::false_type {};
/**
* @copybrief is_applicable
* @tparam Func A valid function type.
* @tparam Tuple Tuple-like type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Func, template<typename...> class Tuple, typename... Args>
struct is_applicable<Func, Tuple<Args...>>: std::is_invocable<Func, Args...> {};
/**
* @copybrief is_applicable
* @tparam Func A valid function type.
* @tparam Tuple Tuple-like type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Func, template<typename...> class Tuple, typename... Args>
struct is_applicable<Func, const Tuple<Args...>>: std::is_invocable<Func, Args...> {};
/**
* @brief Helper variable template.
* @tparam Func A valid function type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Func, typename Args>
inline constexpr bool is_applicable_v = is_applicable<Func, Args>::value;
/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */
template<typename, typename, typename>
struct is_applicable_r: std::false_type {};
/**
* @copybrief is_applicable_r
* @tparam Ret The type to which the return type of the function should be
* convertible.
* @tparam Func A valid function type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Ret, typename Func, typename... Args>
struct is_applicable_r<Ret, Func, std::tuple<Args...>>: std::is_invocable_r<Ret, Func, Args...> {};
/**
* @brief Helper variable template.
* @tparam Ret The type to which the return type of the function should be
* convertible.
* @tparam Func A valid function type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Ret, typename Func, typename Args>
inline constexpr bool is_applicable_r_v = is_applicable_r<Ret, Func, Args>::value;
/**
* @brief Provides the member constant `value` to true if a given type is
* complete, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type, typename = void>
struct is_complete: std::false_type {};
/*! @copydoc is_complete */
template<typename Type>
struct is_complete<Type, std::void_t<decltype(sizeof(Type))>>: std::true_type {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_complete_v = is_complete<Type>::value;
/**
* @brief Provides the member constant `value` to true if a given type is an
* iterator, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type, typename = void>
struct is_iterator: std::false_type {};
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename, typename = void>
struct has_iterator_category: std::false_type {};
template<typename Type>
struct has_iterator_category<Type, std::void_t<typename std::iterator_traits<Type>::iterator_category>>: std::true_type {};
} // namespace internal
/*! @endcond */
/*! @copydoc is_iterator */
template<typename Type>
struct is_iterator<Type, std::enable_if_t<!std::is_void_v<std::remove_cv_t<std::remove_pointer_t<Type>>>>>
: internal::has_iterator_category<Type> {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_iterator_v = is_iterator<Type>::value;
/**
* @brief Provides the member constant `value` to true if a given type is both
* an empty and non-final class, false otherwise.
* @tparam Type The type to test
*/
template<typename Type>
struct is_ebco_eligible
: std::bool_constant<std::is_empty_v<Type> && !std::is_final_v<Type>> {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_ebco_eligible_v = is_ebco_eligible<Type>::value;
/**
* @brief Provides the member constant `value` to true if `Type::is_transparent`
* is valid and denotes a type, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type, typename = void>
struct is_transparent: std::false_type {};
/*! @copydoc is_transparent */
template<typename Type>
struct is_transparent<Type, std::void_t<typename Type::is_transparent>>: std::true_type {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_transparent_v = is_transparent<Type>::value;
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename, typename = void>
struct has_tuple_size_value: std::false_type {};
template<typename Type>
struct has_tuple_size_value<Type, std::void_t<decltype(std::tuple_size<const Type>::value)>>: std::true_type {};
template<typename, typename = void>
struct has_value_type: std::false_type {};
template<typename Type>
struct has_value_type<Type, std::void_t<typename Type::value_type>>: std::true_type {};
template<typename>
[[nodiscard]] constexpr bool dispatch_is_equality_comparable();
template<typename Type, std::size_t... Index>
[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence<Index...>) {
return (dispatch_is_equality_comparable<std::tuple_element_t<Index, Type>>() && ...);
}
template<typename>
[[nodiscard]] constexpr bool maybe_equality_comparable(char) {
return false;
}
template<typename Type>
[[nodiscard]] constexpr auto maybe_equality_comparable(int) -> decltype(std::declval<Type>() == std::declval<Type>()) {
return true;
}
template<typename Type>
[[nodiscard]] constexpr bool dispatch_is_equality_comparable() {
// NOLINTBEGIN(modernize-use-transparent-functors)
if constexpr(std::is_array_v<Type>) {
return false;
} else if constexpr(is_complete_v<std::tuple_size<std::remove_cv_t<Type>>>) {
if constexpr(has_tuple_size_value<Type>::value) {
return maybe_equality_comparable<Type>(0) && unpack_maybe_equality_comparable<Type>(std::make_index_sequence<std::tuple_size<Type>::value>{});
} else {
return maybe_equality_comparable<Type>(0);
}
} else if constexpr(has_value_type<Type>::value) {
if constexpr(is_iterator_v<Type> || std::is_same_v<typename Type::value_type, Type> || dispatch_is_equality_comparable<typename Type::value_type>()) {
return maybe_equality_comparable<Type>(0);
} else {
return false;
}
} else {
return maybe_equality_comparable<Type>(0);
}
// NOLINTEND(modernize-use-transparent-functors)
}
} // namespace internal
/*! @endcond */
/**
* @brief Provides the member constant `value` to true if a given type is
* equality comparable, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type>
struct is_equality_comparable: std::bool_constant<internal::dispatch_is_equality_comparable<Type>()> {};
/*! @copydoc is_equality_comparable */
template<typename Type>
struct is_equality_comparable<const Type>: is_equality_comparable<Type> {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_equality_comparable_v = is_equality_comparable<Type>::value;
/**
* @brief Transcribes the constness of a type to another type.
* @tparam To The type to which to transcribe the constness.
* @tparam From The type from which to transcribe the constness.
*/
template<typename To, typename From>
struct constness_as {
/*! @brief The type resulting from the transcription of the constness. */
using type = std::remove_const_t<To>;
};
/*! @copydoc constness_as */
template<typename To, typename From>
struct constness_as<To, const From> {
/*! @brief The type resulting from the transcription of the constness. */
using type = const To;
};
/**
* @brief Alias template to facilitate the transcription of the constness.
* @tparam To The type to which to transcribe the constness.
* @tparam From The type from which to transcribe the constness.
*/
template<typename To, typename From>
using constness_as_t = typename constness_as<To, From>::type;
/**
* @brief Extracts the class of a non-static member object or function.
* @tparam Member A pointer to a non-static member object or function.
*/
template<typename Member>
class member_class {
static_assert(std::is_member_pointer_v<Member>, "Invalid pointer type to non-static member object or function");
template<typename Class, typename Ret, typename... Args>
static Class *clazz(Ret (Class::*)(Args...));
template<typename Class, typename Ret, typename... Args>
static Class *clazz(Ret (Class::*)(Args...) const);
template<typename Class, typename Type>
static Class *clazz(Type Class::*);
public:
/*! @brief The class of the given non-static member object or function. */
using type = std::remove_pointer_t<decltype(clazz(std::declval<Member>()))>;
};
/**
* @brief Helper type.
* @tparam Member A pointer to a non-static member object or function.
*/
template<typename Member>
using member_class_t = typename member_class<Member>::type;
/**
* @brief Extracts the n-th argument of a _callable_ type.
* @tparam Index The index of the argument to extract.
* @tparam Candidate A valid _callable_ type.
*/
template<std::size_t Index, typename Candidate>
class nth_argument {
template<typename Ret, typename... Args>
static constexpr type_list<Args...> pick_up(Ret (*)(Args...));
template<typename Ret, typename Class, typename... Args>
static constexpr type_list<Args...> pick_up(Ret (Class ::*)(Args...));
template<typename Ret, typename Class, typename... Args>
static constexpr type_list<Args...> pick_up(Ret (Class ::*)(Args...) const);
template<typename Type, typename Class>
static constexpr type_list<Type> pick_up(Type Class ::*);
template<typename Type>
static constexpr decltype(pick_up(&Type::operator())) pick_up(Type &&);
public:
/*! @brief N-th argument of the _callable_ type. */
using type = type_list_element_t<Index, decltype(pick_up(std::declval<Candidate>()))>;
};
/**
* @brief Helper type.
* @tparam Index The index of the argument to extract.
* @tparam Candidate A valid function, member function or data member type.
*/
template<std::size_t Index, typename Candidate>
using nth_argument_t = typename nth_argument<Index, Candidate>::type;
} // namespace entt
template<typename... Type>
struct std::tuple_size<entt::type_list<Type...>>: std::integral_constant<std::size_t, entt::type_list<Type...>::size> {};
template<std::size_t Index, typename... Type>
struct std::tuple_element<Index, entt::type_list<Type...>>: entt::type_list_element<Index, entt::type_list<Type...>> {};
template<auto... Value>
struct std::tuple_size<entt::value_list<Value...>>: std::integral_constant<std::size_t, entt::value_list<Value...>::size> {};
template<std::size_t Index, auto... Value>
struct std::tuple_element<Index, entt::value_list<Value...>>: entt::value_list_element<Index, entt::value_list<Value...>> {};
#endif

View File

@@ -0,0 +1,101 @@
#ifndef ENTT_CORE_UTILITY_HPP
#define ENTT_CORE_UTILITY_HPP
#include <type_traits>
#include <utility>
namespace entt {
/*! @brief Identity function object (waiting for C++20). */
struct identity {
/*! @brief Indicates that this is a transparent function object. */
using is_transparent = void;
/**
* @brief Returns its argument unchanged.
* @tparam Type Type of the argument.
* @param value The actual argument.
* @return The submitted value as-is.
*/
template<typename Type>
[[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept {
return std::forward<Type>(value);
}
};
/**
* @brief Constant utility to disambiguate overloaded members of a class.
* @tparam Type Type of the desired overload.
* @tparam Class Type of class to which the member belongs.
* @param member A valid pointer to a member.
* @return Pointer to the member.
*/
template<typename Type, typename Class>
[[nodiscard]] constexpr auto overload(Type Class::*member) noexcept {
return member;
}
/**
* @brief Constant utility to disambiguate overloaded functions.
* @tparam Func Function type of the desired overload.
* @param func A valid pointer to a function.
* @return Pointer to the function.
*/
template<typename Func>
[[nodiscard]] constexpr auto overload(Func *func) noexcept {
return func;
}
/**
* @brief Helper type for visitors.
* @tparam Func Types of function objects.
*/
template<typename... Func>
struct overloaded: Func... {
using Func::operator()...;
};
/**
* @brief Deduction guide.
* @tparam Func Types of function objects.
*/
template<typename... Func>
overloaded(Func...) -> overloaded<Func...>;
/**
* @brief Basic implementation of a y-combinator.
* @tparam Func Type of a potentially recursive function.
*/
template<typename Func>
struct y_combinator {
/**
* @brief Constructs a y-combinator from a given function.
* @param recursive A potentially recursive function.
*/
constexpr y_combinator(Func recursive) noexcept(std::is_nothrow_move_constructible_v<Func>)
: func{std::move(recursive)} {}
/**
* @brief Invokes a y-combinator and therefore its underlying function.
* @tparam Args Types of arguments to use to invoke the underlying function.
* @param args Parameters to use to invoke the underlying function.
* @return Return value of the underlying function, if any.
*/
template<typename... Args>
constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v<Func, const y_combinator &, Args...>) {
return func(*this, std::forward<Args>(args)...);
}
/*! @copydoc operator()() */
template<typename... Args>
constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v<Func, y_combinator &, Args...>) {
return func(*this, std::forward<Args>(args)...);
}
private:
Func func;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,59 @@
#ifndef ENTT_ENTITY_COMPONENT_HPP
#define ENTT_ENTITY_COMPONENT_HPP
#include <cstddef>
#include <type_traits>
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Type, typename = void>
struct in_place_delete: std::bool_constant<!(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>)> {};
template<>
struct in_place_delete<void>: std::false_type {};
template<typename Type>
struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
: std::true_type {};
template<typename Type, typename = void>
struct page_size: std::integral_constant<std::size_t, !std::is_empty_v<ENTT_ETO_TYPE(Type)> * ENTT_PACKED_PAGE> {};
template<>
struct page_size<void>: std::integral_constant<std::size_t, 0u> {};
template<typename Type>
struct page_size<Type, std::void_t<decltype(Type::page_size)>>
: std::integral_constant<std::size_t, Type::page_size> {};
} // namespace internal
/*! @endcond */
/**
* @brief Common way to access various properties of components.
* @tparam Type Element type.
* @tparam Entity A valid entity type.
*/
template<typename Type, typename Entity, typename>
struct component_traits {
static_assert(std::is_same_v<std::decay_t<Type>, Type>, "Unsupported type");
/*! @brief Element type. */
using element_type = Type;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Pointer stability, default is `false`. */
static constexpr bool in_place_delete = internal::in_place_delete<Type>::value;
/*! @brief Page size, default is `ENTT_PACKED_PAGE` for non-empty types. */
static constexpr std::size_t page_size = internal::page_size<Type>::value;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,388 @@
#ifndef ENTT_ENTITY_ENTITY_HPP
#define ENTT_ENTITY_ENTITY_HPP
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include "../config/config.h"
#include "../core/bit.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename, typename = void>
struct entt_traits;
template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>>
: entt_traits<std::underlying_type_t<Type>> {
using value_type = Type;
};
template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>>
: entt_traits<typename Type::entity_type> {
using value_type = Type;
};
template<>
struct entt_traits<std::uint32_t> {
using value_type = std::uint32_t;
using entity_type = std::uint32_t;
using version_type = std::uint16_t;
static constexpr entity_type entity_mask = 0xFFFFF;
static constexpr entity_type version_mask = 0xFFF;
};
template<>
struct entt_traits<std::uint64_t> {
using value_type = std::uint64_t;
using entity_type = std::uint64_t;
using version_type = std::uint32_t;
static constexpr entity_type entity_mask = 0xFFFFFFFF;
static constexpr entity_type version_mask = 0xFFFFFFFF;
};
} // namespace internal
/*! @endcond */
/**
* @brief Common basic entity traits implementation.
* @tparam Traits Actual entity traits to use.
*/
template<typename Traits>
class basic_entt_traits {
static constexpr auto length = popcount(Traits::entity_mask);
static_assert(Traits::entity_mask && ((Traits::entity_mask & (Traits::entity_mask + 1)) == 0), "Invalid entity mask");
static_assert((Traits::version_mask & (Traits::version_mask + 1)) == 0, "Invalid version mask");
public:
/*! @brief Value type. */
using value_type = typename Traits::value_type;
/*! @brief Underlying entity type. */
using entity_type = typename Traits::entity_type;
/*! @brief Underlying version type. */
using version_type = typename Traits::version_type;
/*! @brief Entity mask size. */
static constexpr entity_type entity_mask = Traits::entity_mask;
/*! @brief Version mask size */
static constexpr entity_type version_mask = Traits::version_mask;
/**
* @brief Converts an entity to its underlying type.
* @param value The value to convert.
* @return The integral representation of the given value.
*/
[[nodiscard]] static constexpr entity_type to_integral(const value_type value) noexcept {
return static_cast<entity_type>(value);
}
/**
* @brief Returns the entity part once converted to the underlying type.
* @param value The value to convert.
* @return The integral representation of the entity part.
*/
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) noexcept {
return (to_integral(value) & entity_mask);
}
/**
* @brief Returns the version part once converted to the underlying type.
* @param value The value to convert.
* @return The integral representation of the version part.
*/
[[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept {
if constexpr(Traits::version_mask == 0u) {
return version_type{};
} else {
return (static_cast<version_type>(to_integral(value) >> length) & version_mask);
}
}
/**
* @brief Returns the successor of a given identifier.
* @param value The identifier of which to return the successor.
* @return The successor of the given identifier.
*/
[[nodiscard]] static constexpr value_type next(const value_type value) noexcept {
const auto vers = to_version(value) + 1;
return construct(to_integral(value), static_cast<version_type>(vers + (vers == version_mask)));
}
/**
* @brief Constructs an identifier from its parts.
*
* If the version part is not provided, a tombstone is returned.<br/>
* If the entity part is not provided, a null identifier is returned.
*
* @param entity The entity part of the identifier.
* @param version The version part of the identifier.
* @return A properly constructed identifier.
*/
[[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept {
if constexpr(Traits::version_mask == 0u) {
return value_type{entity & entity_mask};
} else {
return value_type{(entity & entity_mask) | (static_cast<entity_type>(version & version_mask) << length)};
}
}
/**
* @brief Combines two identifiers in a single one.
*
* The returned identifier is a copy of the first element except for its
* version, which is taken from the second element.
*
* @param lhs The identifier from which to take the entity part.
* @param rhs The identifier from which to take the version part.
* @return A properly constructed identifier.
*/
[[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) noexcept {
if constexpr(Traits::version_mask == 0u) {
return value_type{lhs & entity_mask};
} else {
return value_type{(lhs & entity_mask) | (rhs & (version_mask << length))};
}
}
};
/**
* @brief Entity traits.
* @tparam Type Type of identifier.
*/
template<typename Type>
struct entt_traits: basic_entt_traits<internal::entt_traits<Type>> {
/*! @brief Base type. */
using base_type = basic_entt_traits<internal::entt_traits<Type>>;
/*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */
static constexpr std::size_t page_size = ENTT_SPARSE_PAGE;
};
/**
* @brief Converts an entity to its underlying type.
* @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the given value.
*/
template<typename Entity>
[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_integral(const Entity value) noexcept {
return entt_traits<Entity>::to_integral(value);
}
/**
* @brief Returns the entity part once converted to the underlying type.
* @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the entity part.
*/
template<typename Entity>
[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_entity(const Entity value) noexcept {
return entt_traits<Entity>::to_entity(value);
}
/**
* @brief Returns the version part once converted to the underlying type.
* @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the version part.
*/
template<typename Entity>
[[nodiscard]] constexpr typename entt_traits<Entity>::version_type to_version(const Entity value) noexcept {
return entt_traits<Entity>::to_version(value);
}
/*! @brief Null object for all identifiers. */
struct null_t {
/**
* @brief Converts the null object to identifiers of any type.
* @tparam Entity Type of identifier.
* @return The null representation for the given type.
*/
template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept {
using traits_type = entt_traits<Entity>;
constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
return value;
}
/**
* @brief Compares two null objects.
* @param other A null object.
* @return True in all cases.
*/
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const null_t other) const noexcept {
return true;
}
/**
* @brief Compares two null objects.
* @param other A null object.
* @return False in all cases.
*/
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const null_t other) const noexcept {
return false;
}
/**
* @brief Compares a null object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using traits_type = entt_traits<Entity>;
return traits_type::to_entity(entity) == traits_type::to_entity(*this);
}
/**
* @brief Compares a null object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity) const noexcept {
return !(entity == *this);
}
};
/**
* @brief Compares a null object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param lhs Identifier with which to compare.
* @param rhs A null object yet to be converted.
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity lhs, const null_t rhs) noexcept {
return rhs.operator==(lhs);
}
/**
* @brief Compares a null object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param lhs Identifier with which to compare.
* @param rhs A null object yet to be converted.
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity lhs, const null_t rhs) noexcept {
return !(rhs == lhs);
}
/*! @brief Tombstone object for all identifiers. */
struct tombstone_t {
/**
* @brief Converts the tombstone object to identifiers of any type.
* @tparam Entity Type of identifier.
* @return The tombstone representation for the given type.
*/
template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept {
using traits_type = entt_traits<Entity>;
constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
return value;
}
/**
* @brief Compares two tombstone objects.
* @param other A tombstone object.
* @return True in all cases.
*/
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const tombstone_t other) const noexcept {
return true;
}
/**
* @brief Compares two tombstone objects.
* @param other A tombstone object.
* @return False in all cases.
*/
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const noexcept {
return false;
}
/**
* @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using traits_type = entt_traits<Entity>;
if constexpr(traits_type::version_mask == 0u) {
return false;
} else {
return (traits_type::to_version(entity) == traits_type::to_version(*this));
}
}
/**
* @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity) const noexcept {
return !(entity == *this);
}
};
/**
* @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param lhs Identifier with which to compare.
* @param rhs A tombstone object yet to be converted.
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity lhs, const tombstone_t rhs) noexcept {
return rhs.operator==(lhs);
}
/**
* @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param lhs Identifier with which to compare.
* @param rhs A tombstone object yet to be converted.
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity lhs, const tombstone_t rhs) noexcept {
return !(rhs == lhs);
}
/**
* @brief Compile-time constant for null entities.
*
* There exist implicit conversions from this variable to identifiers of any
* allowed type. Similarly, there exist comparison operators between the null
* entity and any other identifier.
*/
inline constexpr null_t null{};
/**
* @brief Compile-time constant for tombstone entities.
*
* There exist implicit conversions from this variable to identifiers of any
* allowed type. Similarly, there exist comparison operators between the
* tombstone entity and any other identifier.
*/
inline constexpr tombstone_t tombstone{};
} // namespace entt
#endif

View File

@@ -0,0 +1,290 @@
#ifndef ENTT_ENTITY_FWD_HPP
#define ENTT_ENTITY_FWD_HPP
#include <cstdint>
#include <memory>
#include <type_traits>
#include "../config/config.h"
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
namespace entt {
/*! @brief Default entity identifier. */
enum class entity : id_type {};
/*! @brief Storage deletion policy. */
enum class deletion_policy : std::uint8_t {
/*! @brief Swap-and-pop deletion policy. */
swap_and_pop = 0u,
/*! @brief In-place deletion policy. */
in_place = 1u,
/*! @brief Swap-only deletion policy. */
swap_only = 2u,
/*! @brief Unspecified deletion policy. */
unspecified = swap_and_pop
};
template<typename Type, typename Entity = entity, typename = void>
struct component_traits;
template<typename Entity = entity, typename = std::allocator<Entity>>
class basic_sparse_set;
template<typename Type, typename = entity, typename = std::allocator<Type>, typename = void>
class basic_storage;
template<typename, typename>
class basic_sigh_mixin;
template<typename, typename>
class basic_reactive_mixin;
template<typename Entity = entity, typename = std::allocator<Entity>>
class basic_registry;
template<typename, typename, typename = void>
class basic_view;
template<typename Type, typename = std::allocator<Type *>>
class basic_runtime_view;
template<typename, typename, typename>
class basic_group;
template<typename>
class basic_organizer;
template<typename, typename...>
class basic_handle;
template<typename>
class basic_snapshot;
template<typename>
class basic_snapshot_loader;
template<typename>
class basic_continuous_loader;
/*! @brief Alias declaration for the most common use case. */
using sparse_set = basic_sparse_set<>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Element type.
*/
template<typename Type>
using storage = basic_storage<Type>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Underlying storage type.
*/
template<typename Type>
using sigh_mixin = basic_sigh_mixin<Type, basic_registry<typename Type::entity_type, typename Type::base_type::allocator_type>>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Underlying storage type.
*/
template<typename Type>
using reactive_mixin = basic_reactive_mixin<Type, basic_registry<typename Type::entity_type, typename Type::base_type::allocator_type>>;
/*! @brief Alias declaration for the most common use case. */
using registry = basic_registry<>;
/*! @brief Alias declaration for the most common use case. */
using organizer = basic_organizer<registry>;
/*! @brief Alias declaration for the most common use case. */
using handle = basic_handle<registry>;
/*! @brief Alias declaration for the most common use case. */
using const_handle = basic_handle<const registry>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Args Other template parameters.
*/
template<typename... Args>
using handle_view = basic_handle<registry, Args...>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Args Other template parameters.
*/
template<typename... Args>
using const_handle_view = basic_handle<const registry, Args...>;
/*! @brief Alias declaration for the most common use case. */
using snapshot = basic_snapshot<registry>;
/*! @brief Alias declaration for the most common use case. */
using snapshot_loader = basic_snapshot_loader<registry>;
/*! @brief Alias declaration for the most common use case. */
using continuous_loader = basic_continuous_loader<registry>;
/*! @brief Alias declaration for the most common use case. */
using runtime_view = basic_runtime_view<sparse_set>;
/*! @brief Alias declaration for the most common use case. */
using const_runtime_view = basic_runtime_view<const sparse_set>;
/**
* @brief Alias for exclusion lists.
* @tparam Type List of types.
*/
template<typename... Type>
struct exclude_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr exclude_t() = default;
};
/**
* @brief Variable template for exclusion lists.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr exclude_t<Type...> exclude{};
/**
* @brief Alias for lists of observed elements.
* @tparam Type List of types.
*/
template<typename... Type>
struct get_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr get_t() = default;
};
/**
* @brief Variable template for lists of observed elements.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr get_t<Type...> get{};
/**
* @brief Alias for lists of owned elements.
* @tparam Type List of types.
*/
template<typename... Type>
struct owned_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr owned_t() = default;
};
/**
* @brief Variable template for lists of owned elements.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr owned_t<Type...> owned{};
/**
* @brief Applies a given _function_ to a get list and generate a new list.
* @tparam Type Types provided by the get list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<get_t<Type...>, Op> {
/*! @brief Resulting get list after applying the transform function. */
using type = get_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an exclude list and generate a new list.
* @tparam Type Types provided by the exclude list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<exclude_t<Type...>, Op> {
/*! @brief Resulting exclude list after applying the transform function. */
using type = exclude_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an owned list and generate a new list.
* @tparam Type Types provided by the owned list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<owned_t<Type...>, Op> {
/*! @brief Resulting owned list after applying the transform function. */
using type = owned_t<typename Op<Type>::type...>;
};
/**
* @brief Provides a common way to define storage types.
* @tparam Type Storage value type.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<Type>, typename = void>
struct storage_type {
/*! @brief Type-to-storage conversion result. */
using type = ENTT_STORAGE(sigh_mixin, basic_storage<Type, Entity, Allocator>);
};
/*! @brief Empty value type for reactive storage types. */
struct reactive final {};
/**
* @ brief Partial specialization for reactive storage types.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Allocator>
struct storage_type<reactive, Entity, Allocator> {
/*! @brief Type-to-storage conversion result. */
using type = ENTT_STORAGE(reactive_mixin, basic_storage<reactive, Entity, Allocator>);
};
/**
* @brief Helper type.
* @tparam Args Arguments to forward.
*/
template<typename... Args>
using storage_type_t = typename storage_type<Args...>::type;
/**
* Type-to-storage conversion utility that preserves constness.
* @tparam Type Storage value type, eventually const.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<std::remove_const_t<Type>>>
struct storage_for {
/*! @brief Type-to-storage conversion result. */
using type = constness_as_t<storage_type_t<std::remove_const_t<Type>, Entity, Allocator>, Type>;
};
/**
* @brief Helper type.
* @tparam Args Arguments to forward.
*/
template<typename... Args>
using storage_for_t = typename storage_for<Args...>::type;
/**
* @brief Alias declaration for the most common use case.
* @tparam Get Types of storage iterated by the view.
* @tparam Exclude Types of storage used to filter the view.
*/
template<typename Get, typename Exclude = exclude_t<>>
using view = basic_view<type_list_transform_t<Get, storage_for>, type_list_transform_t<Exclude, storage_for>>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Owned Types of storage _owned_ by the group.
* @tparam Get Types of storage _observed_ by the group.
* @tparam Exclude Types of storage used to filter the group.
*/
template<typename Owned, typename Get = get_t<>, typename Exclude = exclude_t<>>
using group = basic_group<type_list_transform_t<Owned, storage_for>, type_list_transform_t<Get, storage_for>, type_list_transform_t<Exclude, storage_for>>;
} // namespace entt
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,431 @@
#ifndef ENTT_ENTITY_HANDLE_HPP
#define ENTT_ENTITY_HANDLE_HPP
#include <iterator>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/iterator.hpp"
#include "../core/type_traits.hpp"
#include "entity.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename It>
class handle_storage_iterator final {
template<typename Other>
friend class handle_storage_iterator;
using underlying_type = std::remove_reference_t<typename It::value_type::second_type>;
using entity_type = typename underlying_type::entity_type;
public:
using value_type = typename std::iterator_traits<It>::value_type;
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
using iterator_concept = std::forward_iterator_tag;
constexpr handle_storage_iterator() noexcept
: entt{null},
it{},
last{} {}
constexpr handle_storage_iterator(entity_type value, It from, It to) noexcept
: entt{value},
it{from},
last{to} {
while(it != last && !it->second.contains(entt)) {
++it;
}
}
constexpr handle_storage_iterator &operator++() noexcept {
for(++it; it != last && !it->second.contains(entt); ++it) {}
return *this;
}
constexpr handle_storage_iterator operator++(int) noexcept {
const handle_storage_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return *it;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return operator*();
}
template<typename ILhs, typename IRhs>
friend constexpr bool operator==(const handle_storage_iterator<ILhs> &, const handle_storage_iterator<IRhs> &) noexcept;
private:
entity_type entt;
It it;
It last;
};
template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator==(const handle_storage_iterator<ILhs> &lhs, const handle_storage_iterator<IRhs> &rhs) noexcept {
return lhs.it == rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator!=(const handle_storage_iterator<ILhs> &lhs, const handle_storage_iterator<IRhs> &rhs) noexcept {
return !(lhs == rhs);
}
} // namespace internal
/*! @endcond */
/**
* @brief Non-owning handle to an entity.
*
* Tiny wrapper around a registry and an entity.
*
* @tparam Registry Basic registry type.
* @tparam Scope Types to which to restrict the scope of a handle.
*/
template<typename Registry, typename... Scope>
class basic_handle {
using traits_type = entt_traits<typename Registry::entity_type>;
[[nodiscard]] auto &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return static_cast<Registry &>(*owner);
}
public:
/*! @brief Type of registry accepted by the handle. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename traits_type::value_type;
/*! @brief Underlying version type. */
using version_type = typename traits_type::version_type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Iterable handle type. */
using iterable = iterable_adaptor<internal::handle_storage_iterator<typename decltype(std::declval<registry_type>().storage())::iterator>>;
/*! @brief Constructs an invalid handle. */
basic_handle() noexcept
: owner{},
entt{null} {}
/**
* @brief Constructs a handle from a given registry and entity.
* @param ref An instance of the registry class.
* @param value A valid identifier.
*/
basic_handle(registry_type &ref, entity_type value) noexcept
: owner{&ref},
entt{value} {}
/**
* @brief Returns an iterable object to use to _visit_ a handle.
*
* The iterable object returns a pair that contains the name and a reference
* to the current storage.<br/>
* Returned storage are those that contain the entity associated with the
* handle.
*
* @return An iterable object to use to _visit_ the handle.
*/
[[nodiscard]] iterable storage() const noexcept {
auto underlying = owner_or_assert().storage();
return iterable{{entt, underlying.begin(), underlying.end()}, {entt, underlying.end(), underlying.end()}};
}
/*! @copydoc valid */
[[nodiscard]] explicit operator bool() const noexcept {
return owner && owner->valid(entt);
}
/**
* @brief Checks if a handle refers to a valid registry and entity.
* @return True if the handle refers to a valid registry and entity, false
* otherwise.
*/
[[nodiscard]] bool valid() const {
return static_cast<bool>(*this);
}
/**
* @brief Returns a pointer to the underlying registry, if any.
* @return A pointer to the underlying registry, if any.
*/
[[nodiscard]] registry_type *registry() const noexcept {
return owner;
}
/**
* @brief Returns the entity associated with a handle.
* @return The entity associated with the handle.
*/
[[nodiscard]] entity_type entity() const noexcept {
return entt;
}
/*! @copydoc entity */
[[nodiscard]] operator entity_type() const noexcept {
return entity();
}
/*! @brief Destroys the entity associated with a handle. */
void destroy() {
owner_or_assert().destroy(std::exchange(entt, null));
}
/**
* @brief Destroys the entity associated with a handle.
* @param version A desired version upon destruction.
*/
void destroy(const version_type version) {
owner_or_assert().destroy(std::exchange(entt, null), version);
}
/**
* @brief Assigns the given element to a handle.
* @tparam Type Type of element to create.
* @tparam Args Types of arguments to use to construct the element.
* @param args Parameters to use to initialize the element.
* @return A reference to the newly created element.
*/
template<typename Type, typename... Args>
// NOLINTNEXTLINE(modernize-use-nodiscard)
decltype(auto) emplace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type");
return owner_or_assert().template emplace<Type>(entt, std::forward<Args>(args)...);
}
/**
* @brief Assigns or replaces the given element for a handle.
* @tparam Type Type of element to assign or replace.
* @tparam Args Types of arguments to use to construct the element.
* @param args Parameters to use to initialize the element.
* @return A reference to the newly created element.
*/
template<typename Type, typename... Args>
decltype(auto) emplace_or_replace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type");
return owner_or_assert().template emplace_or_replace<Type>(entt, std::forward<Args>(args)...);
}
/**
* @brief Patches the given element for a handle.
* @tparam Type Type of element to patch.
* @tparam Func Types of the function objects to invoke.
* @param func Valid function objects.
* @return A reference to the patched element.
*/
template<typename Type, typename... Func>
decltype(auto) patch(Func &&...func) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type");
return owner_or_assert().template patch<Type>(entt, std::forward<Func>(func)...);
}
/**
* @brief Replaces the given element for a handle.
* @tparam Type Type of element to replace.
* @tparam Args Types of arguments to use to construct the element.
* @param args Parameters to use to initialize the element.
* @return A reference to the element being replaced.
*/
template<typename Type, typename... Args>
decltype(auto) replace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type");
return owner_or_assert().template replace<Type>(entt, std::forward<Args>(args)...);
}
/**
* @brief Removes the given elements from a handle.
* @tparam Type Types of elements to remove.
* @return The number of elements actually removed.
*/
template<typename... Type>
// NOLINTNEXTLINE(modernize-use-nodiscard)
size_type remove() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
return owner_or_assert().template remove<Type...>(entt);
}
/**
* @brief Erases the given elements from a handle.
* @tparam Type Types of elements to erase.
*/
template<typename... Type>
void erase() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
owner_or_assert().template erase<Type...>(entt);
}
/**
* @brief Checks if a handle has all the given elements.
* @tparam Type Elements for which to perform the check.
* @return True if the handle has all the elements, false otherwise.
*/
template<typename... Type>
[[nodiscard]] decltype(auto) all_of() const {
return owner_or_assert().template all_of<Type...>(entt);
}
/**
* @brief Checks if a handle has at least one of the given elements.
* @tparam Type Elements for which to perform the check.
* @return True if the handle has at least one of the given elements,
* false otherwise.
*/
template<typename... Type>
[[nodiscard]] decltype(auto) any_of() const {
return owner_or_assert().template any_of<Type...>(entt);
}
/**
* @brief Returns references to the given elements for a handle.
* @tparam Type Types of elements to get.
* @return References to the elements owned by the handle.
*/
template<typename... Type>
[[nodiscard]] decltype(auto) get() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
return owner_or_assert().template get<Type...>(entt);
}
/**
* @brief Returns a reference to the given element for a handle.
* @tparam Type Type of element to get.
* @tparam Args Types of arguments to use to construct the element.
* @param args Parameters to use to initialize the element.
* @return Reference to the element owned by the handle.
*/
template<typename Type, typename... Args>
[[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type");
return owner_or_assert().template get_or_emplace<Type>(entt, std::forward<Args>(args)...);
}
/**
* @brief Returns pointers to the given elements for a handle.
* @tparam Type Types of elements to get.
* @return Pointers to the elements owned by the handle.
*/
template<typename... Type>
[[nodiscard]] auto try_get() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
return owner_or_assert().template try_get<Type...>(entt);
}
/**
* @brief Checks if a handle has elements assigned.
* @return True if the handle has no elements assigned, false otherwise.
*/
[[nodiscard]] bool orphan() const {
return owner_or_assert().orphan(entt);
}
/**
* @brief Returns a const handle from a non-const one.
* @tparam Other A valid entity type.
* @tparam Args Scope of the handle to construct.
* @return A const handle referring to the same registry and the same
* entity.
*/
template<typename Other, typename... Args>
operator basic_handle<Other, Args...>() const noexcept {
static_assert(std::is_same_v<Other, Registry> || std::is_same_v<std::remove_const_t<Other>, Registry>, "Invalid conversion between different handles");
static_assert((sizeof...(Scope) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Scope)) && ... && (type_list_contains_v<type_list<Scope...>, Args>))), "Invalid conversion between different handles");
return owner ? basic_handle<Other, Args...>{*owner, entt} : basic_handle<Other, Args...>{};
}
private:
registry_type *owner;
entity_type entt;
};
/**
* @brief Compares two handles.
* @tparam Args Scope of the first handle.
* @tparam Other Scope of the second handle.
* @param lhs A valid handle.
* @param rhs A valid handle.
* @return True if both handles refer to the same registry and the same
* entity, false otherwise.
*/
template<typename... Args, typename... Other>
[[nodiscard]] bool operator==(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) noexcept {
return lhs.registry() == rhs.registry() && lhs.entity() == rhs.entity();
}
/**
* @brief Compares two handles.
* @tparam Args Scope of the first handle.
* @tparam Other Scope of the second handle.
* @param lhs A valid handle.
* @param rhs A valid handle.
* @return False if both handles refer to the same registry and the same
* entity, true otherwise.
*/
template<typename... Args, typename... Other>
[[nodiscard]] bool operator!=(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) noexcept {
return !(lhs == rhs);
}
/**
* @brief Compares a handle with the null object.
* @tparam Args Scope of the handle.
* @param lhs A valid handle.
* @param rhs A null object yet to be converted.
* @return False if the two elements differ, true otherwise.
*/
template<typename... Args>
[[nodiscard]] constexpr bool operator==(const basic_handle<Args...> &lhs, const null_t rhs) noexcept {
return (lhs.entity() == rhs);
}
/**
* @brief Compares a handle with the null object.
* @tparam Args Scope of the handle.
* @param lhs A null object yet to be converted.
* @param rhs A valid handle.
* @return False if the two elements differ, true otherwise.
*/
template<typename... Args>
[[nodiscard]] constexpr bool operator==(const null_t lhs, const basic_handle<Args...> &rhs) noexcept {
return (rhs == lhs);
}
/**
* @brief Compares a handle with the null object.
* @tparam Args Scope of the handle.
* @param lhs A valid handle.
* @param rhs A null object yet to be converted.
* @return True if the two elements differ, false otherwise.
*/
template<typename... Args>
[[nodiscard]] constexpr bool operator!=(const basic_handle<Args...> &lhs, const null_t rhs) noexcept {
return (lhs.entity() != rhs);
}
/**
* @brief Compares a handle with the null object.
* @tparam Args Scope of the handle.
* @param lhs A null object yet to be converted.
* @param rhs A valid handle.
* @return True if the two elements differ, false otherwise.
*/
template<typename... Args>
[[nodiscard]] constexpr bool operator!=(const null_t lhs, const basic_handle<Args...> &rhs) noexcept {
return (rhs != lhs);
}
} // namespace entt
#endif

View File

@@ -0,0 +1,256 @@
#ifndef ENTT_ENTITY_HELPER_HPP
#define ENTT_ENTITY_HELPER_HPP
#include <memory>
#include <type_traits>
#include <utility>
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "component.hpp"
#include "fwd.hpp"
#include "group.hpp"
#include "storage.hpp"
#include "view.hpp"
namespace entt {
/**
* @brief Converts a registry to a view.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class as_view {
template<typename... Get, typename... Exclude>
[[nodiscard]] auto dispatch(get_t<Get...>, exclude_t<Exclude...>) const {
return reg->template view<constness_as_t<typename Get::element_type, Get>...>(exclude_t<constness_as_t<typename Exclude::element_type, Exclude>...>{});
}
public:
/*! @brief Type of registry to convert. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
/**
* @brief Constructs a converter for a given registry.
* @param source A valid reference to a registry.
*/
as_view(registry_type &source) noexcept
: reg{&source} {}
/**
* @brief Conversion function from a registry to a view.
* @tparam Get Type of storage used to construct the view.
* @tparam Exclude Types of storage used to filter the view.
* @return A newly created view.
*/
template<typename Get, typename Exclude>
operator basic_view<Get, Exclude>() const {
return dispatch(Get{}, Exclude{});
}
private:
registry_type *reg;
};
/**
* @brief Converts a registry to a group.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class as_group {
template<typename... Owned, typename... Get, typename... Exclude>
[[nodiscard]] auto dispatch(owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>) const {
if constexpr(std::is_const_v<registry_type>) {
return reg->template group_if_exists<typename Owned::element_type...>(get_t<typename Get::element_type...>{}, exclude_t<typename Exclude::element_type...>{});
} else {
return reg->template group<constness_as_t<typename Owned::element_type, Owned>...>(get_t<constness_as_t<typename Get::element_type, Get>...>{}, exclude_t<constness_as_t<typename Exclude::element_type, Exclude>...>{});
}
}
public:
/*! @brief Type of registry to convert. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
/**
* @brief Constructs a converter for a given registry.
* @param source A valid reference to a registry.
*/
as_group(registry_type &source) noexcept
: reg{&source} {}
/**
* @brief Conversion function from a registry to a group.
* @tparam Owned Types of _owned_ by the group.
* @tparam Get Types of storage _observed_ by the group.
* @tparam Exclude Types of storage used to filter the group.
* @return A newly created group.
*/
template<typename Owned, typename Get, typename Exclude>
operator basic_group<Owned, Get, Exclude>() const {
return dispatch(Owned{}, Get{}, Exclude{});
}
private:
registry_type *reg;
};
/**
* @brief Helper to create a listener that directly invokes a member function.
* @tparam Member Member function to invoke on an element of the given type.
* @tparam Registry Basic registry type.
* @param reg A registry that contains the given entity and its elements.
* @param entt Entity from which to get the element.
*/
template<auto Member, typename Registry = std::decay_t<nth_argument_t<0u, decltype(Member)>>>
void invoke(Registry &reg, const typename Registry::entity_type entt) {
static_assert(std::is_member_function_pointer_v<decltype(Member)>, "Invalid pointer to non-static member function");
(reg.template get<member_class_t<decltype(Member)>>(entt).*Member)(reg, entt);
}
/**
* @brief Returns the entity associated with a given element.
*
* @warning
* Currently, this function only works correctly with the default storage as it
* makes assumptions about how the elements are laid out.
*
* @tparam Args Storage type template parameters.
* @param storage A storage that contains the given element.
* @param instance A valid element instance.
* @return The entity associated with the given element.
*/
template<typename... Args>
typename basic_storage<Args...>::entity_type to_entity(const basic_storage<Args...> &storage, const typename basic_storage<Args...>::value_type &instance) {
using traits_type = component_traits<typename basic_storage<Args...>::value_type, typename basic_storage<Args...>::entity_type>;
static_assert(traits_type::page_size != 0u, "Unexpected page size");
const auto *page = storage.raw();
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(std::size_t pos{}, count = storage.size(); pos < count; pos += traits_type::page_size, ++page) {
if(const auto dist = (std::addressof(instance) - *page); dist >= 0 && dist < static_cast<decltype(dist)>(traits_type::page_size)) {
return *(static_cast<const typename basic_storage<Args...>::base_type &>(storage).rbegin() + static_cast<decltype(dist)>(pos) + dist);
}
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
return null;
}
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct sigh_helper;
/**
* @brief Signal connection helper for registries.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
struct sigh_helper<Registry> {
/*! @brief Registry type. */
using registry_type = Registry;
/**
* @brief Constructs a helper for a given registry.
* @param ref A valid reference to a registry.
*/
sigh_helper(registry_type &ref)
: bucket{&ref} {}
/**
* @brief Binds a properly initialized helper to a given signal type.
* @tparam Type Type of signal to bind the helper to.
* @param id Optional name for the underlying storage to use.
* @return A helper for a given registry and signal type.
*/
template<typename Type>
auto with(const id_type id = type_hash<Type>::value()) noexcept {
return sigh_helper<registry_type, Type>{*bucket, id};
}
/**
* @brief Returns a reference to the underlying registry.
* @return A reference to the underlying registry.
*/
[[nodiscard]] registry_type &registry() noexcept {
return *bucket;
}
private:
registry_type *bucket;
};
/**
* @brief Signal connection helper for registries.
* @tparam Registry Basic registry type.
* @tparam Type Type of signal to connect listeners to.
*/
template<typename Registry, typename Type>
struct sigh_helper<Registry, Type> final: sigh_helper<Registry> {
/*! @brief Registry type. */
using registry_type = Registry;
/**
* @brief Constructs a helper for a given registry.
* @param ref A valid reference to a registry.
* @param id Optional name for the underlying storage to use.
*/
sigh_helper(registry_type &ref, const id_type id = type_hash<Type>::value())
: sigh_helper<Registry>{ref},
name{id} {}
/**
* @brief Forwards the call to `on_construct` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_construct(Args &&...args) {
this->registry().template on_construct<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
/**
* @brief Forwards the call to `on_update` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_update(Args &&...args) {
this->registry().template on_update<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
/**
* @brief Forwards the call to `on_destroy` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_destroy(Args &&...args) {
this->registry().template on_destroy<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
private:
id_type name;
};
/**
* @brief Deduction guide.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
sigh_helper(Registry &) -> sigh_helper<Registry>;
} // namespace entt
#endif

View File

@@ -0,0 +1,599 @@
#ifndef ENTT_ENTITY_MIXIN_HPP
#define ENTT_ENTITY_MIXIN_HPP
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/any.hpp"
#include "../core/type_info.hpp"
#include "../signal/sigh.hpp"
#include "entity.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename, typename, typename = void>
struct has_on_construct final: std::false_type {};
template<typename Type, typename Registry>
struct has_on_construct<Type, Registry, std::void_t<decltype(Type::on_construct(std::declval<Registry &>(), std::declval<Registry>().create()))>>
: std::true_type {};
template<typename, typename, typename = void>
struct has_on_update final: std::false_type {};
template<typename Type, typename Registry>
struct has_on_update<Type, Registry, std::void_t<decltype(Type::on_update(std::declval<Registry &>(), std::declval<Registry>().create()))>>
: std::true_type {};
template<typename, typename, typename = void>
struct has_on_destroy final: std::false_type {};
template<typename Type, typename Registry>
struct has_on_destroy<Type, Registry, std::void_t<decltype(Type::on_destroy(std::declval<Registry &>(), std::declval<Registry>().create()))>>
: std::true_type {};
} // namespace internal
/*! @endcond */
/**
* @brief Mixin type used to add signal support to storage types.
*
* The function type of a listener is equivalent to:
*
* @code{.cpp}
* void(basic_registry<entity_type> &, entity_type);
* @endcode
*
* This applies to all signals made available.
*
* @tparam Type Underlying storage type.
* @tparam Registry Basic registry type.
*/
template<typename Type, typename Registry>
class basic_sigh_mixin final: public Type {
using underlying_type = Type;
using owner_type = Registry;
using basic_registry_type = basic_registry<typename owner_type::entity_type, typename owner_type::allocator_type>;
using sigh_type = sigh<void(owner_type &, const typename underlying_type::entity_type), typename underlying_type::allocator_type>;
using underlying_iterator = typename underlying_type::base_type::basic_iterator;
static_assert(std::is_base_of_v<basic_registry_type, owner_type>, "Invalid registry type");
[[nodiscard]] auto &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return static_cast<owner_type &>(*owner);
}
private:
void pop(underlying_iterator first, underlying_iterator last) final {
if(auto &reg = owner_or_assert(); destruction.empty()) {
underlying_type::pop(first, last);
} else {
for(; first != last; ++first) {
const auto entt = *first;
destruction.publish(reg, entt);
const auto it = underlying_type::find(entt);
underlying_type::pop(it, it + 1u);
}
}
}
void pop_all() final {
if(auto &reg = owner_or_assert(); !destruction.empty()) {
if constexpr(std::is_same_v<typename underlying_type::element_type, entity_type>) {
for(typename underlying_type::size_type pos{}, last = underlying_type::free_list(); pos < last; ++pos) {
destruction.publish(reg, underlying_type::base_type::operator[](pos));
}
} else {
for(auto entt: static_cast<typename underlying_type::base_type &>(*this)) {
if constexpr(underlying_type::storage_policy == deletion_policy::in_place) {
if(entt != tombstone) {
destruction.publish(reg, entt);
}
} else {
destruction.publish(reg, entt);
}
}
}
}
underlying_type::pop_all();
}
underlying_iterator try_emplace(const typename underlying_type::entity_type entt, const bool force_back, const void *value) final {
const auto it = underlying_type::try_emplace(entt, force_back, value);
if(auto &reg = owner_or_assert(); it != underlying_type::base_type::end()) {
construction.publish(reg, *it);
}
return it;
}
void bind_any(any value) noexcept final {
owner = any_cast<basic_registry_type>(&value);
if constexpr(!std::is_same_v<registry_type, basic_registry_type>) {
if(owner == nullptr) {
owner = any_cast<registry_type>(&value);
}
}
underlying_type::bind_any(std::move(value));
}
public:
/*! @brief Allocator type. */
using allocator_type = typename underlying_type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename underlying_type::entity_type;
/*! @brief Expected registry type. */
using registry_type = owner_type;
/*! @brief Default constructor. */
basic_sigh_mixin()
: basic_sigh_mixin{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_sigh_mixin(const allocator_type &allocator)
: underlying_type{allocator},
owner{},
construction{allocator},
destruction{allocator},
update{allocator} {
if constexpr(internal::has_on_construct<typename underlying_type::element_type, Registry>::value) {
entt::sink{construction}.template connect<&underlying_type::element_type::on_construct>();
}
if constexpr(internal::has_on_update<typename underlying_type::element_type, Registry>::value) {
entt::sink{update}.template connect<&underlying_type::element_type::on_update>();
}
if constexpr(internal::has_on_destroy<typename underlying_type::element_type, Registry>::value) {
entt::sink{destruction}.template connect<&underlying_type::element_type::on_destroy>();
}
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_sigh_mixin(const basic_sigh_mixin &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
// NOLINTBEGIN(bugprone-use-after-move)
basic_sigh_mixin(basic_sigh_mixin &&other) noexcept
: underlying_type{std::move(other)},
owner{other.owner},
construction{std::move(other.construction)},
destruction{std::move(other.destruction)},
update{std::move(other.update)} {}
// NOLINTEND(bugprone-use-after-move)
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
// NOLINTBEGIN(bugprone-use-after-move)
basic_sigh_mixin(basic_sigh_mixin &&other, const allocator_type &allocator)
: underlying_type{std::move(other), allocator},
owner{other.owner},
construction{std::move(other.construction), allocator},
destruction{std::move(other.destruction), allocator},
update{std::move(other.update), allocator} {}
// NOLINTEND(bugprone-use-after-move)
/*! @brief Default destructor. */
~basic_sigh_mixin() override = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This mixin.
*/
basic_sigh_mixin &operator=(const basic_sigh_mixin &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This mixin.
*/
basic_sigh_mixin &operator=(basic_sigh_mixin &&other) noexcept {
swap(other);
return *this;
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(basic_sigh_mixin &other) noexcept {
using std::swap;
swap(owner, other.owner);
swap(construction, other.construction);
swap(destruction, other.destruction);
swap(update, other.update);
underlying_type::swap(other);
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever a new instance is created and assigned to an entity.<br/>
* Listeners are invoked after the object has been assigned to the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_construct() noexcept {
return sink{construction};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is explicitly updated.<br/>
* Listeners are invoked after the object has been updated.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_update() noexcept {
return sink{update};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is removed from an entity and thus destroyed.<br/>
* Listeners are invoked before the object has been removed from the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_destroy() noexcept {
return sink{destruction};
}
/**
* @brief Checks if a mixin refers to a valid registry.
* @return True if the mixin refers to a valid registry, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return (owner != nullptr);
}
/**
* @brief Returns a pointer to the underlying registry, if any.
* @return A pointer to the underlying registry, if any.
*/
[[nodiscard]] const registry_type &registry() const noexcept {
return owner_or_assert();
}
/*! @copydoc registry */
[[nodiscard]] registry_type &registry() noexcept {
return owner_or_assert();
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
* @return A valid identifier.
*/
auto generate() {
const auto entt = underlying_type::generate();
construction.publish(owner_or_assert(), entt);
return entt;
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
* @param hint Required identifier.
* @return A valid identifier.
*/
entity_type generate(const entity_type hint) {
const auto entt = underlying_type::generate(hint);
construction.publish(owner_or_assert(), entt);
return entt;
}
/**
* @brief Assigns each element in a range an identifier.
* @tparam It Type of mutable forward iterator.
* @param first An iterator to the first element of the range to generate.
* @param last An iterator past the last element of the range to generate.
*/
template<typename It>
void generate(It first, It last) {
underlying_type::generate(first, last);
if(auto &reg = owner_or_assert(); !construction.empty()) {
for(; first != last; ++first) {
construction.publish(reg, *first);
}
}
}
/**
* @brief Assigns an entity to a storage and constructs its object.
* @tparam Args Types of arguments to forward to the underlying storage.
* @param entt A valid identifier.
* @param args Parameters to forward to the underlying storage.
* @return A reference to the newly created object.
*/
template<typename... Args>
decltype(auto) emplace(const entity_type entt, Args &&...args) {
underlying_type::emplace(entt, std::forward<Args>(args)...);
construction.publish(owner_or_assert(), entt);
return this->get(entt);
}
/**
* @brief Updates the instance assigned to a given entity in-place.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
* @return A reference to the patched instance.
*/
template<typename... Func>
decltype(auto) patch(const entity_type entt, Func &&...func) {
underlying_type::patch(entt, std::forward<Func>(func)...);
update.publish(owner_or_assert(), entt);
return this->get(entt);
}
/**
* @brief Assigns one or more entities to a storage and constructs their
* objects from a given instance.
* @tparam It Type of input iterator.
* @tparam Args Types of arguments to forward to the underlying storage.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @param args Parameters to use to forward to the underlying storage.
*/
template<typename It, typename... Args>
void insert(It first, It last, Args &&...args) {
auto from = underlying_type::size();
underlying_type::insert(first, last, std::forward<Args>(args)...);
if(auto &reg = owner_or_assert(); !construction.empty()) {
// fine as long as insert passes force_back true to try_emplace
for(const auto to = underlying_type::size(); from != to; ++from) {
construction.publish(reg, underlying_type::operator[](from));
}
}
}
private:
basic_registry_type *owner;
sigh_type construction;
sigh_type destruction;
sigh_type update;
};
/**
* @brief Mixin type used to add _reactive_ support to storage types.
* @tparam Type Underlying storage type.
* @tparam Registry Basic registry type.
*/
template<typename Type, typename Registry>
class basic_reactive_mixin final: public Type {
using underlying_type = Type;
using owner_type = Registry;
using alloc_traits = std::allocator_traits<typename underlying_type::allocator_type>;
using basic_registry_type = basic_registry<typename owner_type::entity_type, typename owner_type::allocator_type>;
using container_type = std::vector<connection, typename alloc_traits::template rebind_alloc<connection>>;
static_assert(std::is_base_of_v<basic_registry_type, owner_type>, "Invalid registry type");
[[nodiscard]] auto &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return static_cast<owner_type &>(*owner);
}
void emplace_element(const Registry &, typename underlying_type::entity_type entity) {
if(!underlying_type::contains(entity)) {
underlying_type::emplace(entity);
}
}
private:
void bind_any(any value) noexcept final {
owner = any_cast<basic_registry_type>(&value);
if constexpr(!std::is_same_v<registry_type, basic_registry_type>) {
if(owner == nullptr) {
owner = any_cast<registry_type>(&value);
}
}
underlying_type::bind_any(std::move(value));
}
public:
/*! @brief Allocator type. */
using allocator_type = typename underlying_type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename underlying_type::entity_type;
/*! @brief Expected registry type. */
using registry_type = owner_type;
/*! @brief Default constructor. */
basic_reactive_mixin()
: basic_reactive_mixin{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_reactive_mixin(const allocator_type &allocator)
: underlying_type{allocator},
owner{},
conn{allocator} {
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_reactive_mixin(const basic_reactive_mixin &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
// NOLINTBEGIN(bugprone-use-after-move)
basic_reactive_mixin(basic_reactive_mixin &&other) noexcept
: underlying_type{std::move(other)},
owner{other.owner},
conn{} {
}
// NOLINTEND(bugprone-use-after-move)
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
// NOLINTBEGIN(bugprone-use-after-move)
basic_reactive_mixin(basic_reactive_mixin &&other, const allocator_type &allocator)
: underlying_type{std::move(other), allocator},
owner{other.owner},
conn{allocator} {
}
// NOLINTEND(bugprone-use-after-move)
/*! @brief Default destructor. */
~basic_reactive_mixin() override = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This mixin.
*/
basic_reactive_mixin &operator=(const basic_reactive_mixin &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This mixin.
*/
basic_reactive_mixin &operator=(basic_reactive_mixin &&other) noexcept {
underlying_type::swap(other);
return *this;
}
/**
* @brief Makes storage _react_ to creation of objects of the given type.
* @tparam Clazz Type of element to _react_ to.
* @tparam Candidate Function to use to _react_ to the event.
* @param id Optional name used to map the storage within the registry.
* @return This mixin.
*/
template<typename Clazz, auto Candidate = &basic_reactive_mixin::emplace_element>
basic_reactive_mixin &on_construct(const id_type id = type_hash<Clazz>::value()) {
auto curr = owner_or_assert().template storage<Clazz>(id).on_construct().template connect<Candidate>(*this);
conn.push_back(std::move(curr));
return *this;
}
/**
* @brief Makes storage _react_ to update of objects of the given type.
* @tparam Clazz Type of element to _react_ to.
* @tparam Candidate Function to use to _react_ to the event.
* @param id Optional name used to map the storage within the registry.
* @return This mixin.
*/
template<typename Clazz, auto Candidate = &basic_reactive_mixin::emplace_element>
basic_reactive_mixin &on_update(const id_type id = type_hash<Clazz>::value()) {
auto curr = owner_or_assert().template storage<Clazz>(id).on_update().template connect<Candidate>(*this);
conn.push_back(std::move(curr));
return *this;
}
/**
* @brief Makes storage _react_ to destruction of objects of the given type.
* @tparam Clazz Type of element to _react_ to.
* @tparam Candidate Function to use to _react_ to the event.
* @param id Optional name used to map the storage within the registry.
* @return This mixin.
*/
template<typename Clazz, auto Candidate = &basic_reactive_mixin::emplace_element>
basic_reactive_mixin &on_destroy(const id_type id = type_hash<Clazz>::value()) {
auto curr = owner_or_assert().template storage<Clazz>(id).on_destroy().template connect<Candidate>(*this);
conn.push_back(std::move(curr));
return *this;
}
/**
* @brief Checks if a mixin refers to a valid registry.
* @return True if the mixin refers to a valid registry, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return (owner != nullptr);
}
/**
* @brief Returns a pointer to the underlying registry, if any.
* @return A pointer to the underlying registry, if any.
*/
[[nodiscard]] const registry_type &registry() const noexcept {
return owner_or_assert();
}
/*! @copydoc registry */
[[nodiscard]] registry_type &registry() noexcept {
return owner_or_assert();
}
/**
* @brief Returns a view that is filtered by the underlying storage.
* @tparam Get Types of elements used to construct the view.
* @tparam Exclude Types of elements used to filter the view.
* @return A newly created view.
*/
template<typename... Get, typename... Exclude>
[[nodiscard]] basic_view<get_t<const basic_reactive_mixin, typename basic_registry_type::template storage_for_type<const Get>...>, exclude_t<typename basic_registry_type::template storage_for_type<const Exclude>...>>
view(exclude_t<Exclude...> = exclude_t{}) const {
const owner_type &parent = owner_or_assert();
basic_view<get_t<const basic_reactive_mixin, typename basic_registry_type::template storage_for_type<const Get>...>, exclude_t<typename basic_registry_type::template storage_for_type<const Exclude>...>> elem{};
[&elem](const auto *...curr) { ((curr ? elem.storage(*curr) : void()), ...); }(parent.template storage<std::remove_const_t<Exclude>>()..., parent.template storage<std::remove_const_t<Get>>()..., this);
return elem;
}
/*! @copydoc view */
template<typename... Get, typename... Exclude>
[[nodiscard]] basic_view<get_t<const basic_reactive_mixin, typename basic_registry_type::template storage_for_type<Get>...>, exclude_t<typename basic_registry_type::template storage_for_type<Exclude>...>>
view(exclude_t<Exclude...> = exclude_t{}) {
std::conditional_t<((std::is_const_v<Get> && ...) && (std::is_const_v<Exclude> && ...)), const owner_type, owner_type> &parent = owner_or_assert();
return {*this, parent.template storage<std::remove_const_t<Get>>()..., parent.template storage<std::remove_const_t<Exclude>>()...};
}
/*! @brief Releases all connections to the underlying registry, if any. */
void reset() {
for(auto &&curr: conn) {
curr.release();
}
conn.clear();
}
private:
basic_registry_type *owner;
container_type conn;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,439 @@
#ifndef ENTT_ENTITY_ORGANIZER_HPP
#define ENTT_ENTITY_ORGANIZER_HPP
#include <cstddef>
#include <type_traits>
#include <utility>
#include <vector>
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "../core/utility.hpp"
#include "../graph/adjacency_matrix.hpp"
#include "../graph/flow.hpp"
#include "fwd.hpp"
#include "helper.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename>
struct is_view: std::false_type {};
template<typename... Args>
struct is_view<basic_view<Args...>>: std::true_type {};
template<typename Type>
inline constexpr bool is_view_v = is_view<Type>::value;
template<typename>
struct is_group: std::false_type {};
template<typename... Args>
struct is_group<basic_group<Args...>>: std::true_type {};
template<typename Type>
inline constexpr bool is_group_v = is_group<Type>::value;
template<typename Type, typename Override>
struct unpack_type {
using ro = std::conditional_t<
type_list_contains_v<Override, const Type> || (std::is_const_v<Type> && !type_list_contains_v<Override, std::remove_const_t<Type>>),
type_list<std::remove_const_t<Type>>,
type_list<>>;
using rw = std::conditional_t<
type_list_contains_v<Override, std::remove_const_t<Type>> || (!std::is_const_v<Type> && !type_list_contains_v<Override, const Type>),
type_list<Type>,
type_list<>>;
};
template<typename... Args, typename... Override>
struct unpack_type<basic_registry<Args...>, type_list<Override...>> {
using ro = type_list<>;
using rw = type_list<>;
};
template<typename... Args, typename... Override>
struct unpack_type<const basic_registry<Args...>, type_list<Override...>>
: unpack_type<basic_registry<Args...>, type_list<Override...>> {};
template<typename... Get, typename... Exclude, typename... Override>
struct unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {
using ro = type_list_cat_t<type_list<typename Exclude::element_type...>, typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::rw...>;
};
template<typename... Get, typename... Exclude, typename... Override>
struct unpack_type<const basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>>
: unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {};
template<typename... Owned, typename... Get, typename... Exclude, typename... Override>
struct unpack_type<basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {
using ro = type_list_cat_t<type_list<typename Exclude::element_type...>, typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::ro..., typename unpack_type<constness_as_t<typename Owned::element_type, Owned>, type_list<Override...>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::rw..., typename unpack_type<constness_as_t<typename Owned::element_type, Owned>, type_list<Override...>>::rw...>;
};
template<typename... Owned, typename... Get, typename... Exclude, typename... Override>
struct unpack_type<const basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>>
: unpack_type<basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {};
template<typename, typename>
struct resource_traits;
template<typename... Args, typename... Req>
struct resource_traits<type_list<Args...>, type_list<Req...>> {
using args = type_list<std::remove_const_t<Args>...>;
using ro = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::ro..., typename unpack_type<Req, type_list<>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::rw..., typename unpack_type<Req, type_list<>>::rw...>;
};
template<typename... Req, typename Ret, typename... Args>
resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> free_function_to_resource_traits(Ret (*)(Args...));
template<typename... Req, typename Ret, typename Type, typename... Args>
resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (*)(Type &, Args...));
template<typename... Req, typename Ret, typename Class, typename... Args>
resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (Class::*)(Args...));
template<typename... Req, typename Ret, typename Class, typename... Args>
resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (Class::*)(Args...) const);
} // namespace internal
/*! @endcond */
/**
* @brief Utility class for creating a static task graph.
*
* This class offers minimal support (but sufficient in many cases) for creating
* an execution graph from functions and their requirements on resources.<br/>
* Note that the resulting tasks aren't executed in any case. This isn't the
* goal of the tool. Instead, they are returned to the user in the form of a
* graph that allows for safe execution.
*
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class basic_organizer final {
using callback_type = void(const void *, Registry &);
using prepare_type = void(Registry &);
using dependency_type = std::size_t(const bool, const type_info **, const std::size_t);
struct vertex_data final {
std::size_t ro_count{};
std::size_t rw_count{};
const char *name{};
const void *payload{};
callback_type *callback{};
dependency_type *dependency{};
prepare_type *prepare{};
const type_info *info{};
};
template<typename Type>
[[nodiscard]] static decltype(auto) extract(Registry &reg) {
if constexpr(std::is_same_v<Type, Registry>) {
return reg;
} else if constexpr(internal::is_view_v<Type>) {
return static_cast<Type>(as_view{reg});
} else if constexpr(internal::is_group_v<Type>) {
return static_cast<Type>(as_group{reg});
} else {
return reg.ctx().template emplace<std::remove_reference_t<Type>>();
}
}
template<typename... Args>
[[nodiscard]] static auto to_args(Registry &reg, type_list<Args...>) {
return std::tuple<decltype(extract<Args>(reg))...>(extract<Args>(reg)...);
}
template<typename... Type>
[[nodiscard]] static std::size_t fill_dependencies(type_list<Type...>, [[maybe_unused]] const type_info **buffer, [[maybe_unused]] const std::size_t count) {
if constexpr(sizeof...(Type) == 0u) {
return {};
} else {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
const type_info *info[]{&type_id<Type>()...};
const auto length = count < sizeof...(Type) ? count : sizeof...(Type);
for(std::size_t pos{}; pos < length; ++pos) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
buffer[pos] = info[pos];
}
return length;
}
}
template<typename... RO, typename... RW>
void track_dependencies(std::size_t index, const bool requires_registry, type_list<RO...>, type_list<RW...>) {
builder.bind(static_cast<id_type>(index));
builder.set(type_hash<Registry>::value(), requires_registry || (sizeof...(RO) + sizeof...(RW) == 0u));
(builder.ro(type_hash<RO>::value()), ...);
(builder.rw(type_hash<RW>::value()), ...);
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Raw task function type. */
using function_type = callback_type;
/*! @brief Vertex type of a task graph defined as an adjacency list. */
struct vertex {
/**
* @brief Constructs a vertex of the task graph.
* @param data The data associated with the vertex.
* @param from List of in-edges of the vertex.
* @param to List of out-edges of the vertex.
*/
vertex(vertex_data data, std::vector<std::size_t> from, std::vector<std::size_t> to)
: node{std::move(data)},
in{std::move(from)},
out{std::move(to)} {}
/**
* @brief Fills a buffer with the type info objects for the writable
* resources of a vertex.
* @param buffer A buffer pre-allocated by the user.
* @param length The length of the user-supplied buffer.
* @return The number of type info objects written to the buffer.
*/
[[nodiscard]] size_type ro_dependency(const type_info **buffer, const std::size_t length) const noexcept {
return node.dependency(false, buffer, length);
}
/**
* @brief Fills a buffer with the type info objects for the read-only
* resources of a vertex.
* @param buffer A buffer pre-allocated by the user.
* @param length The length of the user-supplied buffer.
* @return The number of type info objects written to the buffer.
*/
[[nodiscard]] size_type rw_dependency(const type_info **buffer, const std::size_t length) const noexcept {
return node.dependency(true, buffer, length);
}
/**
* @brief Returns the number of read-only resources of a vertex.
* @return The number of read-only resources of the vertex.
*/
[[nodiscard]] size_type ro_count() const noexcept {
return node.ro_count;
}
/**
* @brief Returns the number of writable resources of a vertex.
* @return The number of writable resources of the vertex.
*/
[[nodiscard]] size_type rw_count() const noexcept {
return node.rw_count;
}
/**
* @brief Checks if a vertex is also a top-level one.
* @return True if the vertex is a top-level one, false otherwise.
*/
[[nodiscard]] bool top_level() const noexcept {
return in.empty();
}
/**
* @brief Returns a type info object associated with a vertex.
* @return A properly initialized type info object.
*/
[[nodiscard]] const type_info &info() const noexcept {
return *node.info;
}
/**
* @brief Returns a user defined name associated with a vertex, if any.
* @return The user defined name associated with the vertex, if any.
*/
[[nodiscard]] const char *name() const noexcept {
return node.name;
}
/**
* @brief Returns the function associated with a vertex.
* @return The function associated with the vertex.
*/
[[nodiscard]] function_type *callback() const noexcept {
return node.callback;
}
/**
* @brief Returns the payload associated with a vertex, if any.
* @return The payload associated with the vertex, if any.
*/
[[nodiscard]] const void *data() const noexcept {
return node.payload;
}
/**
* @brief Returns the list of in-edges of a vertex.
* @return The list of in-edges of a vertex.
*/
[[nodiscard]] const std::vector<std::size_t> &in_edges() const noexcept {
return in;
}
/**
* @brief Returns the list of out-edges of a vertex.
* @return The list of out-edges of a vertex.
*/
[[nodiscard]] const std::vector<std::size_t> &out_edges() const noexcept {
return out;
}
/**
* @brief Prepares a registry and assures that all required resources
* are properly instantiated before using them.
* @param reg A valid registry.
*/
void prepare(registry_type &reg) const {
node.prepare ? node.prepare(reg) : void();
}
private:
vertex_data node;
std::vector<std::size_t> in;
std::vector<std::size_t> out;
};
/**
* @brief Adds a free function to the task list.
* @tparam Candidate Function to add to the task list.
* @tparam Req Additional requirements and/or override resource access mode.
* @param name Optional name to associate with the task.
*/
template<auto Candidate, typename... Req>
void emplace(const char *name = nullptr) {
using resource_type = decltype(internal::free_function_to_resource_traits<Req...>(Candidate));
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, registry_type>;
callback_type *callback = +[](const void *, registry_type &reg) {
std::apply(Candidate, to_args(reg, typename resource_type::args{}));
};
vertex_data vdata{
resource_type::ro::size,
resource_type::rw::size,
name,
nullptr,
callback,
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
+[](registry_type &reg) { void(to_args(reg, typename resource_type::args{})); },
&type_id<std::integral_constant<decltype(Candidate), Candidate>>()};
track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{});
vertices.push_back(std::move(vdata));
}
/**
* @brief Adds a free function with payload or a member function with an
* instance to the task list.
* @tparam Candidate Function or member to add to the task list.
* @tparam Req Additional requirements and/or override resource access mode.
* @tparam Type Type of class or type of payload.
* @param value_or_instance A valid object that fits the purpose.
* @param name Optional name to associate with the task.
*/
template<auto Candidate, typename... Req, typename Type>
void emplace(Type &value_or_instance, const char *name = nullptr) {
using resource_type = decltype(internal::constrained_function_to_resource_traits<Req...>(Candidate));
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, registry_type>;
callback_type *callback = +[](const void *payload, registry_type &reg) {
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
std::apply(Candidate, std::tuple_cat(std::forward_as_tuple(*curr), to_args(reg, typename resource_type::args{})));
};
vertex_data vdata{
resource_type::ro::size,
resource_type::rw::size,
name,
&value_or_instance,
callback,
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
+[](registry_type &reg) { void(to_args(reg, typename resource_type::args{})); },
&type_id<std::integral_constant<decltype(Candidate), Candidate>>()};
track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{});
vertices.push_back(std::move(vdata));
}
/**
* @brief Adds an user defined function with optional payload to the task
* list.
* @tparam Req Additional requirements and/or override resource access mode.
* @param func Function to add to the task list.
* @param payload User defined arbitrary data.
* @param name Optional name to associate with the task.
*/
template<typename... Req>
void emplace(function_type *func, const void *payload = nullptr, const char *name = nullptr) {
using resource_type = internal::resource_traits<type_list<>, type_list<Req...>>;
track_dependencies(vertices.size(), true, typename resource_type::ro{}, typename resource_type::rw{});
vertex_data vdata{
resource_type::ro::size,
resource_type::rw::size,
name,
payload,
func,
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
nullptr,
&type_id<void>()};
vertices.push_back(std::move(vdata));
}
/**
* @brief Generates a task graph for the current content.
* @return The adjacency list of the task graph.
*/
[[nodiscard]] std::vector<vertex> graph() {
std::vector<vertex> adjacency_list{};
adjacency_list.reserve(vertices.size());
auto adjacency_matrix = builder.graph();
for(auto curr: adjacency_matrix.vertices()) {
std::vector<std::size_t> in{};
std::vector<std::size_t> out{};
for(auto &&edge: adjacency_matrix.in_edges(curr)) {
in.push_back(edge.first);
}
for(auto &&edge: adjacency_matrix.out_edges(curr)) {
out.push_back(edge.second);
}
adjacency_list.emplace_back(vertices[curr], std::move(in), std::move(out));
}
return adjacency_list;
}
/*! @brief Erases all elements from a container. */
void clear() {
builder.clear();
vertices.clear();
}
private:
std::vector<vertex_data> vertices;
flow builder;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,26 @@
#ifndef ENTT_ENTITY_RANGES_HPP
#define ENTT_ENTITY_RANGES_HPP
#if __has_include(<version>)
# include <version>
#
# if defined(__cpp_lib_ranges)
# include <ranges>
# include "fwd.hpp"
template<class... Args>
inline constexpr bool std::ranges::enable_borrowed_range<entt::basic_view<Args...>>{true};
template<class... Args>
inline constexpr bool std::ranges::enable_borrowed_range<entt::basic_group<Args...>>{true};
template<class... Args>
inline constexpr bool std::ranges::enable_view<entt::basic_view<Args...>>{true};
template<class... Args>
inline constexpr bool std::ranges::enable_view<entt::basic_group<Args...>>{true};
# endif
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,318 @@
#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP
#define ENTT_ENTITY_RUNTIME_VIEW_HPP
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <utility>
#include <vector>
#include "entity.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Set>
class runtime_view_iterator final {
using iterator_type = typename Set::iterator;
using iterator_traits = std::iterator_traits<iterator_type>;
[[nodiscard]] bool valid() const {
return (!tombstone_check || *it != tombstone)
&& std::all_of(++pools->begin(), pools->end(), [entt = *it](const auto *curr) { return curr->contains(entt); })
&& std::none_of(filter->cbegin(), filter->cend(), [entt = *it](const auto *curr) { return curr && curr->contains(entt); });
}
public:
using value_type = typename iterator_traits::value_type;
using pointer = typename iterator_traits::pointer;
using reference = typename iterator_traits::reference;
using difference_type = typename iterator_traits::difference_type;
using iterator_category = std::bidirectional_iterator_tag;
constexpr runtime_view_iterator() noexcept
: pools{},
filter{},
it{},
tombstone_check{} {}
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
runtime_view_iterator(const std::vector<Set *> &cpools, const std::vector<Set *> &ignore, iterator_type curr) noexcept
: pools{&cpools},
filter{&ignore},
it{curr},
tombstone_check{pools->size() == 1u && (*pools)[0u]->policy() == deletion_policy::in_place} {
if(it != (*pools)[0]->end() && !valid()) {
++(*this);
}
}
runtime_view_iterator &operator++() {
++it;
for(const auto last = (*pools)[0]->end(); it != last && !valid(); ++it) {}
return *this;
}
runtime_view_iterator operator++(int) {
const runtime_view_iterator orig = *this;
return ++(*this), orig;
}
runtime_view_iterator &operator--() {
--it;
for(const auto first = (*pools)[0]->begin(); it != first && !valid(); --it) {}
return *this;
}
runtime_view_iterator operator--(int) {
const runtime_view_iterator orig = *this;
return operator--(), orig;
}
[[nodiscard]] pointer operator->() const noexcept {
return it.operator->();
}
[[nodiscard]] reference operator*() const noexcept {
return *operator->();
}
[[nodiscard]] constexpr bool operator==(const runtime_view_iterator &other) const noexcept {
return it == other.it;
}
[[nodiscard]] constexpr bool operator!=(const runtime_view_iterator &other) const noexcept {
return !(*this == other);
}
private:
const std::vector<Set *> *pools;
const std::vector<Set *> *filter;
iterator_type it;
bool tombstone_check;
};
} // namespace internal
/*! @endcond */
/**
* @brief Generic runtime view.
*
* Runtime views iterate over those entities that are at least in the given
* storage. During initialization, a runtime view looks at the number of
* entities available for each element and uses the smallest set in order to get
* a performance boost when iterating.
*
* @b Important
*
* Iterators aren't invalidated if:
*
* * New elements are added to the storage.
* * The entity currently pointed is modified (for example, elements are added
* or removed from it).
* * The entity currently pointed is destroyed.
*
* In all other cases, modifying the storage iterated by the view in any way
* invalidates all the iterators.
*
* @tparam Type Common base type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Allocator>
class basic_runtime_view {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type *>, "Invalid value type");
using container_type = std::vector<Type *, Allocator>;
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Underlying entity identifier. */
using entity_type = typename Type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Common type among all storage types. */
using common_type = Type;
/*! @brief Bidirectional iterator type. */
using iterator = internal::runtime_view_iterator<common_type>;
/*! @brief Default constructor to use to create empty, invalid views. */
basic_runtime_view() noexcept
: basic_runtime_view{allocator_type{}} {}
/**
* @brief Constructs an empty, invalid view with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_runtime_view(const allocator_type &allocator)
: pools{allocator},
filter{allocator} {}
/*! @brief Default copy constructor. */
basic_runtime_view(const basic_runtime_view &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
basic_runtime_view(const basic_runtime_view &other, const allocator_type &allocator)
: pools{other.pools, allocator},
filter{other.filter, allocator} {}
/*! @brief Default move constructor. */
basic_runtime_view(basic_runtime_view &&) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_runtime_view(basic_runtime_view &&other, const allocator_type &allocator)
: pools{std::move(other.pools), allocator},
filter{std::move(other.filter), allocator} {}
/*! @brief Default destructor. */
~basic_runtime_view() = default;
/**
* @brief Default copy assignment operator.
* @return This runtime view.
*/
basic_runtime_view &operator=(const basic_runtime_view &) = default;
/**
* @brief Default move assignment operator.
* @return This runtime view.
*/
basic_runtime_view &operator=(basic_runtime_view &&) noexcept = default;
/**
* @brief Exchanges the contents with those of a given view.
* @param other View to exchange the content with.
*/
void swap(basic_runtime_view &other) noexcept {
using std::swap;
swap(pools, other.pools);
swap(filter, other.filter);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return pools.get_allocator();
}
/*! @brief Clears the view. */
void clear() {
pools.clear();
filter.clear();
}
/**
* @brief Appends an opaque storage object to a runtime view.
* @param base An opaque reference to a storage object.
* @return This runtime view.
*/
basic_runtime_view &iterate(common_type &base) {
if(pools.empty() || !(base.size() < pools[0u]->size())) {
pools.push_back(&base);
} else {
pools.push_back(std::exchange(pools[0u], &base));
}
return *this;
}
/**
* @brief Adds an opaque storage object as a filter of a runtime view.
* @param base An opaque reference to a storage object.
* @return This runtime view.
*/
basic_runtime_view &exclude(common_type &base) {
filter.push_back(&base);
return *this;
}
/**
* @brief Estimates the number of entities iterated by the view.
* @return Estimated number of entities iterated by the view.
*/
[[nodiscard]] size_type size_hint() const {
return pools.empty() ? size_type{} : pools.front()->size();
}
/**
* @brief Returns an iterator to the first entity that has the given
* elements.
*
* If the view is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first entity that has the given elements.
*/
[[nodiscard]] iterator begin() const {
return pools.empty() ? iterator{} : iterator{pools, filter, pools[0]->begin()};
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given elements.
* @return An iterator to the entity following the last entity that has the
* given elements.
*/
[[nodiscard]] iterator end() const {
return pools.empty() ? iterator{} : iterator{pools, filter, pools[0]->end()};
}
/**
* @brief Checks whether a view is initialized or not.
* @return True if the view is initialized, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return !(pools.empty() && filter.empty());
}
/**
* @brief Checks if a view contains an entity.
* @param entt A valid identifier.
* @return True if the view contains the given entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const {
return !pools.empty()
&& std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *curr) { return curr->contains(entt); })
&& std::none_of(filter.cbegin(), filter.cend(), [entt](const auto *curr) { return curr && curr->contains(entt); });
}
/**
* @brief Iterates entities and applies the given function object to them.
*
* The function object is invoked for each entity. It is provided only with
* the entity itself.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(const entity_type);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) const {
for(const auto entity: *this) {
func(entity);
}
}
private:
container_type pools;
container_type filter;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,512 @@
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
#define ENTT_ENTITY_SNAPSHOT_HPP
#include <cstddef>
#include <iterator>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../container/dense_map.hpp"
#include "../core/type_traits.hpp"
#include "entity.hpp"
#include "fwd.hpp"
#include "view.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Registry>
void orphans(Registry &registry) {
auto &storage = registry.template storage<typename Registry::entity_type>();
for(auto entt: storage) {
if(registry.orphan(entt)) {
storage.erase(entt);
}
}
}
} // namespace internal
/*! @endcond */
/**
* @brief Utility class to create snapshots from a registry.
*
* A _snapshot_ can be either a dump of the entire registry or a narrower
* selection of elements of interest.<br/>
* This type can be used in both cases if provided with a correctly configured
* output archive.
*
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class basic_snapshot {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = entt_traits<typename Registry::entity_type>;
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_snapshot(const registry_type &source) noexcept
: reg{&source} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_snapshot(const basic_snapshot &) = delete;
/*! @brief Default move constructor. */
basic_snapshot(basic_snapshot &&) noexcept = default;
/*! @brief Default destructor. */
~basic_snapshot() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This snapshot.
*/
basic_snapshot &operator=(const basic_snapshot &) = delete;
/**
* @brief Default move assignment operator.
* @return This snapshot.
*/
basic_snapshot &operator=(basic_snapshot &&) noexcept = default;
/**
* @brief Serializes all elements of a type with associated identifiers.
* @tparam Type Type of elements to serialize.
* @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive.
* @param id Optional name used to map the storage within the registry.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Type, typename Archive>
const basic_snapshot &get(Archive &archive, const id_type id = type_hash<Type>::value()) const {
if(const auto *storage = reg->template storage<Type>(id); storage) {
const typename registry_type::common_type &base = *storage;
archive(static_cast<typename traits_type::entity_type>(storage->size()));
if constexpr(std::is_same_v<Type, entity_type>) {
archive(static_cast<typename traits_type::entity_type>(storage->free_list()));
for(auto first = base.rbegin(), last = base.rend(); first != last; ++first) {
archive(*first);
}
} else if constexpr(registry_type::template storage_for_type<Type>::storage_policy == deletion_policy::in_place) {
for(auto it = base.rbegin(), last = base.rend(); it != last; ++it) {
if(const auto entt = *it; entt == tombstone) {
archive(static_cast<entity_type>(null));
} else {
archive(entt);
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, storage->get_as_tuple(entt));
}
}
} else {
for(auto elem: storage->reach()) {
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, elem);
}
}
} else {
archive(typename traits_type::entity_type{});
}
return *this;
}
/**
* @brief Serializes all elements of a type with associated identifiers for
* the entities in a range.
* @tparam Type Type of elements to serialize.
* @tparam Archive Type of output archive.
* @tparam It Type of input iterator.
* @param archive A valid reference to an output archive.
* @param first An iterator to the first element of the range to serialize.
* @param last An iterator past the last element of the range to serialize.
* @param id Optional name used to map the storage within the registry.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Type, typename Archive, typename It>
const basic_snapshot &get(Archive &archive, It first, It last, const id_type id = type_hash<Type>::value()) const {
static_assert(!std::is_same_v<Type, entity_type>, "Entity types not supported");
if(const auto *storage = reg->template storage<Type>(id); storage && !storage->empty()) {
archive(static_cast<typename traits_type::entity_type>(std::distance(first, last)));
for(; first != last; ++first) {
if(const auto entt = *first; storage->contains(entt)) {
archive(entt);
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, storage->get_as_tuple(entt));
} else {
archive(static_cast<entity_type>(null));
}
}
} else {
archive(typename traits_type::entity_type{});
}
return *this;
}
private:
const registry_type *reg;
};
/**
* @brief Utility class to restore a snapshot as a whole.
*
* A snapshot loader requires that the destination registry be empty and loads
* all the data at once while keeping intact the identifiers that the entities
* originally had.<br/>
* An example of use is the implementation of a save/restore utility.
*
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class basic_snapshot_loader {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = entt_traits<typename Registry::entity_type>;
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_snapshot_loader(registry_type &source) noexcept
: reg{&source} {
// restoring a snapshot as a whole requires a clean registry
ENTT_ASSERT(reg->template storage<entity_type>().free_list() == 0u, "Registry must be empty");
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_snapshot_loader(const basic_snapshot_loader &) = delete;
/*! @brief Default move constructor. */
basic_snapshot_loader(basic_snapshot_loader &&) noexcept = default;
/*! @brief Default destructor. */
~basic_snapshot_loader() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This loader.
*/
basic_snapshot_loader &operator=(const basic_snapshot_loader &) = delete;
/**
* @brief Default move assignment operator.
* @return This loader.
*/
basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default;
/**
* @brief Restores all elements of a type with associated identifiers.
* @tparam Type Type of elements to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @param id Optional name used to map the storage within the registry.
* @return A valid loader to continue restoring data.
*/
template<typename Type, typename Archive>
basic_snapshot_loader &get(Archive &archive, const id_type id = type_hash<Type>::value()) {
auto &storage = reg->template storage<Type>(id);
typename traits_type::entity_type length{};
archive(length);
if constexpr(std::is_same_v<Type, entity_type>) {
typename traits_type::entity_type count{};
entity_type placeholder{};
storage.reserve(length);
archive(count);
for(entity_type entity = null; length; --length) {
archive(entity);
storage.generate(entity);
placeholder = (entity > placeholder) ? entity : placeholder;
}
storage.start_from(traits_type::next(placeholder));
storage.free_list(count);
} else {
auto &other = reg->template storage<entity_type>();
entity_type entt{null};
while(length--) {
if(archive(entt); entt != null) {
const auto entity = other.contains(entt) ? entt : other.generate(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
if constexpr(std::tuple_size_v<decltype(storage.get_as_tuple({}))> == 0u) {
storage.emplace(entity);
} else {
Type elem{};
archive(elem);
storage.emplace(entity, std::move(elem));
}
}
}
}
return *this;
}
/**
* @brief Destroys those entities that have no elements.
*
* In case all the entities were serialized but only part of the elements
* was saved, it could happen that some of the entities have no elements
* once restored.<br/>
* This function helps to identify and destroy those entities.
*
* @return A valid loader to continue restoring data.
*/
basic_snapshot_loader &orphans() {
internal::orphans(*reg);
return *this;
}
private:
registry_type *reg;
};
/**
* @brief Utility class for _continuous loading_.
*
* A _continuous loader_ is designed to load data from a source registry to a
* (possibly) non-empty destination. The loader can accommodate in a registry
* more than one snapshot in a sort of _continuous loading_ that updates the
* destination one step at a time.<br/>
* Identifiers that entities originally had are not transferred to the target.
* Instead, the loader maps remote identifiers to local ones while restoring a
* snapshot.<br/>
* An example of use is the implementation of a client-server application with
* the requirement of transferring somehow parts of the representation side to
* side.
*
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class basic_continuous_loader {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = entt_traits<typename Registry::entity_type>;
void restore(typename Registry::entity_type entt) {
if(const auto entity = to_entity(entt); remloc.contains(entity) && remloc[entity].first == entt) {
if(!reg->valid(remloc[entity].second)) {
remloc[entity].second = reg->create();
}
} else {
remloc.insert_or_assign(entity, std::make_pair(entt, reg->create()));
}
}
template<typename Container>
auto update(int, Container &container) -> decltype(typename Container::mapped_type{}, void()) {
// map like container
Container other;
for(auto &&pair: container) {
using first_type = std::remove_const_t<typename std::decay_t<decltype(pair)>::first_type>;
using second_type = typename std::decay_t<decltype(pair)>::second_type;
if constexpr(std::is_same_v<first_type, entity_type> && std::is_same_v<second_type, entity_type>) {
other.emplace(map(pair.first), map(pair.second));
} else if constexpr(std::is_same_v<first_type, entity_type>) {
other.emplace(map(pair.first), std::move(pair.second));
} else {
static_assert(std::is_same_v<second_type, entity_type>, "Neither the key nor the value are of entity type");
other.emplace(std::move(pair.first), map(pair.second));
}
}
using std::swap;
swap(container, other);
}
template<typename Container>
auto update(char, Container &container) -> decltype(typename Container::value_type{}, void()) {
// vector like container
static_assert(std::is_same_v<typename Container::value_type, entity_type>, "Invalid value type");
for(auto &&entt: container) {
entt = map(entt);
}
}
template<typename Component, typename Other, typename Member>
void update([[maybe_unused]] Component &instance, [[maybe_unused]] Member Other::*member) {
if constexpr(!std::is_same_v<Component, Other>) {
return;
} else if constexpr(std::is_same_v<Member, entity_type>) {
instance.*member = map(instance.*member);
} else {
// maybe a container? let's try...
update(0, instance.*member);
}
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_continuous_loader(registry_type &source) noexcept
: remloc{source.get_allocator()},
reg{&source} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_continuous_loader(const basic_continuous_loader &) = delete;
/*! @brief Default move constructor. */
basic_continuous_loader(basic_continuous_loader &&) noexcept = default;
/*! @brief Default destructor. */
~basic_continuous_loader() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This loader.
*/
basic_continuous_loader &operator=(const basic_continuous_loader &) = delete;
/**
* @brief Default move assignment operator.
* @return This loader.
*/
basic_continuous_loader &operator=(basic_continuous_loader &&) noexcept = default;
/**
* @brief Restores all elements of a type with associated identifiers.
*
* It creates local counterparts for remote elements as needed.<br/>
* Members are either data members of type entity_type or containers of
* entities. In both cases, a loader visits them and replaces entities with
* their local counterpart.
*
* @tparam Type Type of elements to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @param id Optional name used to map the storage within the registry.
* @return A valid loader to continue restoring data.
*/
template<typename Type, typename Archive>
basic_continuous_loader &get(Archive &archive, const id_type id = type_hash<Type>::value()) {
auto &storage = reg->template storage<Type>(id);
typename traits_type::entity_type length{};
entity_type entt{null};
archive(length);
if constexpr(std::is_same_v<Type, entity_type>) {
typename traits_type::entity_type in_use{};
storage.reserve(length);
archive(in_use);
for(std::size_t pos{}; pos < in_use; ++pos) {
archive(entt);
restore(entt);
}
for(std::size_t pos = in_use; pos < length; ++pos) {
archive(entt);
if(const auto entity = to_entity(entt); remloc.contains(entity)) {
if(reg->valid(remloc[entity].second)) {
reg->destroy(remloc[entity].second);
}
remloc.erase(entity);
}
}
} else {
for(auto &&ref: remloc) {
storage.remove(ref.second.second);
}
while(length--) {
if(archive(entt); entt != null) {
restore(entt);
if constexpr(std::tuple_size_v<decltype(storage.get_as_tuple({}))> == 0u) {
storage.emplace(map(entt));
} else {
Type elem{};
archive(elem);
storage.emplace(map(entt), std::move(elem));
}
}
}
}
return *this;
}
/**
* @brief Destroys those entities that have no elements.
*
* In case all the entities were serialized but only part of the elements
* was saved, it could happen that some of the entities have no elements
* once restored.<br/>
* This function helps to identify and destroy those entities.
*
* @return A non-const reference to this loader.
*/
basic_continuous_loader &orphans() {
internal::orphans(*reg);
return *this;
}
/**
* @brief Tests if a loader knows about a given entity.
* @param entt A valid identifier.
* @return True if `entity` is managed by the loader, false otherwise.
*/
[[nodiscard]] bool contains(entity_type entt) const noexcept {
const auto it = remloc.find(to_entity(entt));
return it != remloc.cend() && it->second.first == entt;
}
/**
* @brief Returns the identifier to which an entity refers.
* @param entt A valid identifier.
* @return The local identifier if any, the null entity otherwise.
*/
[[nodiscard]] entity_type map(entity_type entt) const noexcept {
if(const auto it = remloc.find(to_entity(entt)); it != remloc.cend() && it->second.first == entt) {
return it->second.second;
}
return null;
}
private:
dense_map<typename traits_type::entity_type, std::pair<entity_type, entity_type>> remloc;
registry_type *reg;
};
} // namespace entt
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1139
src/external/entt/src/entt/entity/view.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

69
src/external/entt/src/entt/entt.hpp vendored Normal file
View File

@@ -0,0 +1,69 @@
/*! @brief `EnTT` default namespace. */
namespace entt {}
// IWYU pragma: begin_exports
#include "config/config.h"
#include "config/macro.h"
#include "config/version.h"
#include "container/dense_map.hpp"
#include "container/dense_set.hpp"
#include "container/table.hpp"
#include "core/algorithm.hpp"
#include "core/any.hpp"
#include "core/attribute.h"
#include "core/bit.hpp"
#include "core/compressed_pair.hpp"
#include "core/enum.hpp"
#include "core/family.hpp"
#include "core/hashed_string.hpp"
#include "core/ident.hpp"
#include "core/iterator.hpp"
#include "core/memory.hpp"
#include "core/monostate.hpp"
#include "core/ranges.hpp"
#include "core/tuple.hpp"
#include "core/type_info.hpp"
#include "core/type_traits.hpp"
#include "core/utility.hpp"
#include "entity/component.hpp"
#include "entity/entity.hpp"
#include "entity/group.hpp"
#include "entity/handle.hpp"
#include "entity/helper.hpp"
#include "entity/mixin.hpp"
#include "entity/organizer.hpp"
#include "entity/ranges.hpp"
#include "entity/registry.hpp"
#include "entity/runtime_view.hpp"
#include "entity/snapshot.hpp"
#include "entity/sparse_set.hpp"
#include "entity/storage.hpp"
#include "entity/view.hpp"
#include "graph/adjacency_matrix.hpp"
#include "graph/dot.hpp"
#include "graph/flow.hpp"
#include "locator/locator.hpp"
#include "meta/adl_pointer.hpp"
#include "meta/container.hpp"
#include "meta/context.hpp"
#include "meta/factory.hpp"
#include "meta/meta.hpp"
#include "meta/node.hpp"
#include "meta/pointer.hpp"
#include "meta/policy.hpp"
#include "meta/range.hpp"
#include "meta/resolve.hpp"
#include "meta/template.hpp"
#include "meta/type_traits.hpp"
#include "meta/utility.hpp"
#include "poly/poly.hpp"
#include "process/process.hpp"
#include "process/scheduler.hpp"
#include "resource/cache.hpp"
#include "resource/loader.hpp"
#include "resource/resource.hpp"
#include "signal/delegate.hpp"
#include "signal/dispatcher.hpp"
#include "signal/emitter.hpp"
#include "signal/sigh.hpp"
// IWYU pragma: end_exports

11
src/external/entt/src/entt/fwd.hpp vendored Normal file
View File

@@ -0,0 +1,11 @@
// IWYU pragma: begin_exports
#include "container/fwd.hpp"
#include "core/fwd.hpp"
#include "entity/fwd.hpp"
#include "graph/fwd.hpp"
#include "meta/fwd.hpp"
#include "poly/fwd.hpp"
#include "process/fwd.hpp"
#include "resource/fwd.hpp"
#include "signal/fwd.hpp"
// IWYU pragma: end_exports

View File

@@ -0,0 +1,341 @@
#ifndef ENTT_GRAPH_ADJACENCY_MATRIX_HPP
#define ENTT_GRAPH_ADJACENCY_MATRIX_HPP
#include <cstddef>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../core/iterator.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename It>
class edge_iterator {
using size_type = std::size_t;
void find_next() noexcept {
for(; pos != last && !it[static_cast<typename It::difference_type>(pos)]; pos += offset) {}
}
public:
using value_type = std::pair<size_type, size_type>;
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
using iterator_concept = std::forward_iterator_tag;
constexpr edge_iterator() noexcept = default;
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
constexpr edge_iterator(It base, const size_type vertices, const size_type from, const size_type to, const size_type step) noexcept
: it{std::move(base)},
vert{vertices},
pos{from},
last{to},
offset{step} {
find_next();
}
constexpr edge_iterator &operator++() noexcept {
pos += offset;
find_next();
return *this;
}
constexpr edge_iterator operator++(int) noexcept {
const edge_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return *operator->();
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return std::make_pair<size_type>(pos / vert, pos % vert);
}
template<typename Type>
friend constexpr bool operator==(const edge_iterator<Type> &, const edge_iterator<Type> &) noexcept;
private:
It it{};
size_type vert{};
size_type pos{};
size_type last{};
size_type offset{};
};
template<typename Container>
[[nodiscard]] constexpr bool operator==(const edge_iterator<Container> &lhs, const edge_iterator<Container> &rhs) noexcept {
return lhs.pos == rhs.pos;
}
template<typename Container>
[[nodiscard]] constexpr bool operator!=(const edge_iterator<Container> &lhs, const edge_iterator<Container> &rhs) noexcept {
return !(lhs == rhs);
}
} // namespace internal
/*! @endcond */
/**
* @brief Basic implementation of a directed adjacency matrix.
* @tparam Category Either a directed or undirected category tag.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Category, typename Allocator>
class adjacency_matrix {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_base_of_v<directed_tag, Category>, "Invalid graph category");
static_assert(std::is_same_v<typename alloc_traits::value_type, std::size_t>, "Invalid value type");
using container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Vertex type. */
using vertex_type = size_type;
/*! @brief Edge type. */
using edge_type = std::pair<vertex_type, vertex_type>;
/*! @brief Vertex iterator type. */
using vertex_iterator = iota_iterator<vertex_type>;
/*! @brief Edge iterator type. */
using edge_iterator = internal::edge_iterator<typename container_type::const_iterator>;
/*! @brief Out-edge iterator type. */
using out_edge_iterator = edge_iterator;
/*! @brief In-edge iterator type. */
using in_edge_iterator = edge_iterator;
/*! @brief Graph category tag. */
using graph_category = Category;
/*! @brief Default constructor. */
adjacency_matrix() noexcept(noexcept(allocator_type{}))
: adjacency_matrix{0u} {
}
/**
* @brief Constructs an empty container with a given allocator.
* @param allocator The allocator to use.
*/
explicit adjacency_matrix(const allocator_type &allocator) noexcept
: adjacency_matrix{0u, allocator} {}
/**
* @brief Constructs an empty container with a given allocator and user
* supplied number of vertices.
* @param vertices Number of vertices.
* @param allocator The allocator to use.
*/
adjacency_matrix(const size_type vertices, const allocator_type &allocator = allocator_type{})
: matrix{vertices * vertices, allocator},
vert{vertices} {}
/*! @brief Default copy constructor. */
adjacency_matrix(const adjacency_matrix &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
adjacency_matrix(const adjacency_matrix &other, const allocator_type &allocator)
: matrix{other.matrix, allocator},
vert{other.vert} {}
/*! @brief Default move constructor. */
adjacency_matrix(adjacency_matrix &&) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
adjacency_matrix(adjacency_matrix &&other, const allocator_type &allocator)
: matrix{std::move(other.matrix), allocator},
vert{other.vert} {}
/*! @brief Default destructor. */
~adjacency_matrix() = default;
/**
* @brief Default copy assignment operator.
* @return This container.
*/
adjacency_matrix &operator=(const adjacency_matrix &) = default;
/**
* @brief Default move assignment operator.
* @return This container.
*/
adjacency_matrix &operator=(adjacency_matrix &&) noexcept = default;
/**
* @brief Exchanges the contents with those of a given adjacency matrix.
* @param other Adjacency matrix to exchange the content with.
*/
void swap(adjacency_matrix &other) noexcept {
using std::swap;
swap(matrix, other.matrix);
swap(vert, other.vert);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return matrix.get_allocator();
}
/*! @brief Clears the adjacency matrix. */
void clear() noexcept {
matrix.clear();
vert = {};
}
/**
* @brief Returns true if an adjacency matrix is empty, false otherwise.
*
* @warning
* Potentially expensive, try to avoid it on hot paths.
*
* @return True if the adjacency matrix is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
const auto iterable = edges();
return (iterable.begin() == iterable.end());
}
/**
* @brief Returns the number of vertices.
* @return The number of vertices.
*/
[[nodiscard]] size_type size() const noexcept {
return vert;
}
/**
* @brief Returns an iterable object to visit all vertices of a matrix.
* @return An iterable object to visit all vertices of a matrix.
*/
[[nodiscard]] iterable_adaptor<vertex_iterator> vertices() const noexcept {
return {0u, vert};
}
/**
* @brief Returns an iterable object to visit all edges of a matrix.
* @return An iterable object to visit all edges of a matrix.
*/
[[nodiscard]] iterable_adaptor<edge_iterator> edges() const noexcept {
const auto it = matrix.cbegin();
const auto sz = matrix.size();
return {{it, vert, 0u, sz, 1u}, {it, vert, sz, sz, 1u}};
}
/**
* @brief Returns an iterable object to visit all out-edges of a vertex.
* @param vertex The vertex of which to return all out-edges.
* @return An iterable object to visit all out-edges of a vertex.
*/
[[nodiscard]] iterable_adaptor<out_edge_iterator> out_edges(const vertex_type vertex) const noexcept {
const auto it = matrix.cbegin();
const auto from = vertex * vert;
const auto to = from + vert;
return {{it, vert, from, to, 1u}, {it, vert, to, to, 1u}};
}
/**
* @brief Returns an iterable object to visit all in-edges of a vertex.
* @param vertex The vertex of which to return all in-edges.
* @return An iterable object to visit all in-edges of a vertex.
*/
[[nodiscard]] iterable_adaptor<in_edge_iterator> in_edges(const vertex_type vertex) const noexcept {
const auto it = matrix.cbegin();
const auto from = vertex;
const auto to = vert * vert + from;
return {{it, vert, from, to, vert}, {it, vert, to, to, vert}};
}
/**
* @brief Resizes an adjacency matrix.
* @param vertices The new number of vertices.
*/
void resize(const size_type vertices) {
adjacency_matrix other{vertices, get_allocator()};
for(auto [lhs, rhs]: edges()) {
other.insert(lhs, rhs);
}
other.swap(*this);
}
/**
* @brief Inserts an edge into the adjacency matrix, if it does not exist.
* @param lhs The left hand vertex of the edge.
* @param rhs The right hand vertex of the edge.
* @return A pair consisting of an iterator to the inserted element (or to
* the element that prevented the insertion) and a bool denoting whether the
* insertion took place.
*/
std::pair<edge_iterator, bool> insert(const vertex_type lhs, const vertex_type rhs) {
const auto pos = lhs * vert + rhs;
if constexpr(std::is_same_v<graph_category, undirected_tag>) {
const auto rev = rhs * vert + lhs;
ENTT_ASSERT(matrix[pos] == matrix[rev], "Something went really wrong");
matrix[rev] = 1u;
}
const auto inserted = !std::exchange(matrix[pos], 1u);
return {edge_iterator{matrix.cbegin(), vert, pos, matrix.size(), 1u}, inserted};
}
/**
* @brief Removes the edge associated with a pair of given vertices.
* @param lhs The left hand vertex of the edge.
* @param rhs The right hand vertex of the edge.
* @return Number of elements removed (either 0 or 1).
*/
size_type erase(const vertex_type lhs, const vertex_type rhs) {
const auto pos = lhs * vert + rhs;
if constexpr(std::is_same_v<graph_category, undirected_tag>) {
const auto rev = rhs * vert + lhs;
ENTT_ASSERT(matrix[pos] == matrix[rev], "Something went really wrong");
matrix[rev] = 0u;
}
return std::exchange(matrix[pos], 0u);
}
/**
* @brief Checks if an adjacency matrix contains a given edge.
* @param lhs The left hand vertex of the edge.
* @param rhs The right hand vertex of the edge.
* @return True if there is such an edge, false otherwise.
*/
[[nodiscard]] bool contains(const vertex_type lhs, const vertex_type rhs) const {
const auto pos = lhs * vert + rhs;
return pos < matrix.size() && matrix[pos];
}
private:
container_type matrix;
size_type vert;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,58 @@
#ifndef ENTT_GRAPH_DOT_HPP
#define ENTT_GRAPH_DOT_HPP
#include <ostream>
#include <type_traits>
#include "fwd.hpp"
namespace entt {
/**
* @brief Outputs a graph in dot format.
* @tparam Graph Graph type, valid as long as it exposes edges and vertices.
* @tparam Writer Vertex decorator type.
* @param out A standard output stream.
* @param graph The graph to output.
* @param writer Vertex decorator object.
*/
template<typename Graph, typename Writer>
void dot(std::ostream &out, const Graph &graph, Writer writer) {
static_assert(std::is_base_of_v<directed_tag, typename Graph::graph_category>, "Invalid graph category");
if constexpr(std::is_same_v<typename Graph::graph_category, undirected_tag>) {
out << "graph{";
} else {
out << "digraph{";
}
for(auto &&vertex: graph.vertices()) {
out << vertex << "[";
writer(out, vertex);
out << "];";
}
for(auto [lhs, rhs]: graph.edges()) {
if constexpr(std::is_same_v<typename Graph::graph_category, undirected_tag>) {
out << lhs << "--" << rhs << ";";
} else {
out << lhs << "->" << rhs << ";";
}
}
out << "}";
}
/**
* @brief Outputs a graph in dot format.
* @tparam Graph Graph type, valid as long as it exposes edges and vertices.
* @param out A standard output stream.
* @param graph The graph to output.
*/
template<typename Graph>
void dot(std::ostream &out, const Graph &graph) {
return dot(out, graph, [](auto &&...) {});
}
} // namespace entt
#endif

View File

@@ -0,0 +1,351 @@
#ifndef ENTT_GRAPH_FLOW_HPP
#define ENTT_GRAPH_FLOW_HPP
#include <algorithm>
#include <cstddef>
#include <functional>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../container/dense_map.hpp"
#include "../container/dense_set.hpp"
#include "../core/compressed_pair.hpp"
#include "../core/fwd.hpp"
#include "../core/iterator.hpp"
#include "../core/utility.hpp"
#include "adjacency_matrix.hpp"
#include "fwd.hpp"
namespace entt {
/**
* @brief Utility class for creating task graphs.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Allocator>
class basic_flow {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, id_type>, "Invalid value type");
using task_container_type = dense_set<id_type, identity, std::equal_to<>, typename alloc_traits::template rebind_alloc<id_type>>;
using ro_rw_container_type = std::vector<std::pair<std::size_t, bool>, typename alloc_traits::template rebind_alloc<std::pair<std::size_t, bool>>>;
using deps_container_type = dense_map<id_type, ro_rw_container_type, identity, std::equal_to<>, typename alloc_traits::template rebind_alloc<std::pair<const id_type, ro_rw_container_type>>>;
using adjacency_matrix_type = adjacency_matrix<directed_tag, typename alloc_traits::template rebind_alloc<std::size_t>>;
void emplace(const id_type res, const bool is_rw) {
ENTT_ASSERT(index.first() < vertices.size(), "Invalid node");
if(!deps.contains(res) && sync_on != vertices.size()) {
deps[res].emplace_back(sync_on, true);
}
deps[res].emplace_back(index.first(), is_rw);
}
void setup_graph(adjacency_matrix_type &matrix) const {
for(const auto &elem: deps) {
const auto last = elem.second.cend();
auto it = elem.second.cbegin();
while(it != last) {
if(it->second) {
// rw item
if(auto curr = it++; it != last) {
if(it->second) {
matrix.insert(curr->first, it->first);
} else if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
for(; it != next; ++it) {
matrix.insert(curr->first, it->first);
matrix.insert(it->first, next->first);
}
} else {
for(; it != next; ++it) {
matrix.insert(curr->first, it->first);
}
}
}
} else {
// ro item (first iteration only)
if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
for(; it != next; ++it) {
matrix.insert(it->first, next->first);
}
} else {
it = last;
}
}
}
}
}
void transitive_closure(adjacency_matrix_type &matrix) const {
const auto length = matrix.size();
for(std::size_t vk{}; vk < length; ++vk) {
for(std::size_t vi{}; vi < length; ++vi) {
for(std::size_t vj{}; vj < length; ++vj) {
if(matrix.contains(vi, vk) && matrix.contains(vk, vj)) {
matrix.insert(vi, vj);
}
}
}
}
}
void transitive_reduction(adjacency_matrix_type &matrix) const {
const auto length = matrix.size();
for(std::size_t vert{}; vert < length; ++vert) {
matrix.erase(vert, vert);
}
for(std::size_t vj{}; vj < length; ++vj) {
for(std::size_t vi{}; vi < length; ++vi) {
if(matrix.contains(vi, vj)) {
for(std::size_t vk{}; vk < length; ++vk) {
if(matrix.contains(vj, vk)) {
matrix.erase(vi, vk);
}
}
}
}
}
}
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Iterable task list. */
using iterable = iterable_adaptor<typename task_container_type::const_iterator>;
/*! @brief Adjacency matrix type. */
using graph_type = adjacency_matrix_type;
/*! @brief Default constructor. */
basic_flow()
: basic_flow{allocator_type{}} {}
/**
* @brief Constructs a flow builder with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_flow(const allocator_type &allocator)
: index{0u, allocator},
vertices{allocator},
deps{allocator} {}
/*! @brief Default copy constructor. */
basic_flow(const basic_flow &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
basic_flow(const basic_flow &other, const allocator_type &allocator)
: index{other.index.first(), allocator},
vertices{other.vertices, allocator},
deps{other.deps, allocator},
sync_on{other.sync_on} {}
/*! @brief Default move constructor. */
basic_flow(basic_flow &&) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_flow(basic_flow &&other, const allocator_type &allocator)
: index{other.index.first(), allocator},
vertices{std::move(other.vertices), allocator},
deps{std::move(other.deps), allocator},
sync_on{other.sync_on} {}
/*! @brief Default destructor. */
~basic_flow() = default;
/**
* @brief Default copy assignment operator.
* @return This flow builder.
*/
basic_flow &operator=(const basic_flow &) = default;
/**
* @brief Default move assignment operator.
* @return This flow builder.
*/
basic_flow &operator=(basic_flow &&) noexcept = default;
/**
* @brief Exchanges the contents with those of a given flow builder.
* @param other Flow builder to exchange the content with.
*/
void swap(basic_flow &other) noexcept {
using std::swap;
std::swap(index, other.index);
std::swap(vertices, other.vertices);
std::swap(deps, other.deps);
std::swap(sync_on, other.sync_on);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return allocator_type{index.second()};
}
/**
* @brief Returns the identifier at specified location.
* @param pos Position of the identifier to return.
* @return The requested identifier.
*/
[[nodiscard]] id_type operator[](const size_type pos) const {
return vertices.cbegin()[static_cast<typename task_container_type::difference_type>(pos)];
}
/*! @brief Clears the flow builder. */
void clear() noexcept {
index.first() = {};
vertices.clear();
deps.clear();
sync_on = {};
}
/**
* @brief Returns true if a flow builder contains no tasks, false otherwise.
* @return True if the flow builder contains no tasks, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
return vertices.empty();
}
/**
* @brief Returns the number of tasks.
* @return The number of tasks.
*/
[[nodiscard]] size_type size() const noexcept {
return vertices.size();
}
/**
* @brief Binds a task to a flow builder.
* @param value Task identifier.
* @return This flow builder.
*/
basic_flow &bind(const id_type value) {
sync_on += (sync_on == vertices.size());
const auto it = vertices.emplace(value).first;
index.first() = size_type(it - vertices.begin());
return *this;
}
/**
* @brief Turns the current task into a sync point.
* @return This flow builder.
*/
basic_flow &sync() {
ENTT_ASSERT(index.first() < vertices.size(), "Invalid node");
sync_on = index.first();
for(const auto &elem: deps) {
elem.second.emplace_back(sync_on, true);
}
return *this;
}
/**
* @brief Assigns a resource to the current task with a given access mode.
* @param res Resource identifier.
* @param is_rw Access mode.
* @return This flow builder.
*/
basic_flow &set(const id_type res, bool is_rw = false) {
emplace(res, is_rw);
return *this;
}
/**
* @brief Assigns a read-only resource to the current task.
* @param res Resource identifier.
* @return This flow builder.
*/
basic_flow &ro(const id_type res) {
emplace(res, false);
return *this;
}
/**
* @brief Assigns a range of read-only resources to the current task.
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of elements.
* @param last An iterator past the last element of the range of elements.
* @return This flow builder.
*/
template<typename It>
std::enable_if_t<std::is_same_v<std::remove_const_t<typename std::iterator_traits<It>::value_type>, id_type>, basic_flow &>
ro(It first, It last) {
for(; first != last; ++first) {
emplace(*first, false);
}
return *this;
}
/**
* @brief Assigns a writable resource to the current task.
* @param res Resource identifier.
* @return This flow builder.
*/
basic_flow &rw(const id_type res) {
emplace(res, true);
return *this;
}
/**
* @brief Assigns a range of writable resources to the current task.
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of elements.
* @param last An iterator past the last element of the range of elements.
* @return This flow builder.
*/
template<typename It>
std::enable_if_t<std::is_same_v<std::remove_const_t<typename std::iterator_traits<It>::value_type>, id_type>, basic_flow &>
rw(It first, It last) {
for(; first != last; ++first) {
emplace(*first, true);
}
return *this;
}
/**
* @brief Generates a task graph for the current content.
* @return The adjacency matrix of the task graph.
*/
[[nodiscard]] graph_type graph() const {
graph_type matrix{vertices.size(), get_allocator()};
setup_graph(matrix);
transitive_closure(matrix);
transitive_reduction(matrix);
return matrix;
}
private:
compressed_pair<size_type, allocator_type> index;
task_container_type vertices;
deps_container_type deps;
size_type sync_on{};
};
} // namespace entt
#endif

View File

@@ -0,0 +1,27 @@
#ifndef ENTT_GRAPH_FWD_HPP
#define ENTT_GRAPH_FWD_HPP
#include <cstddef>
#include <memory>
#include "../core/fwd.hpp"
namespace entt {
/*! @brief Undirected graph category tag. */
struct directed_tag {};
/*! @brief Directed graph category tag. */
struct undirected_tag: directed_tag {};
template<typename, typename = std::allocator<std::size_t>>
class adjacency_matrix;
template<typename = std::allocator<id_type>>
class basic_flow;
/*! @brief Alias declaration for the most common use case. */
using flow = basic_flow<>;
} // namespace entt
#endif

View File

@@ -0,0 +1,158 @@
#ifndef ENTT_LOCATOR_LOCATOR_HPP
#define ENTT_LOCATOR_LOCATOR_HPP
#include <memory>
#include <utility>
#include "../config/config.h"
namespace entt {
/**
* @brief Service locator, nothing more.
*
* A service locator is used to do what it promises: locate services.<br/>
* Usually service locators are tightly bound to the services they expose and
* thus it's hard to define a general purpose class to do that. This tiny class
* tries to fill the gap and to get rid of the burden of defining a different
* specific locator for each application.
*
* @note
* Users shouldn't retain references to a service. The recommended way is to
* retrieve the service implementation currently set each and every time the
* need for it arises. The risk is to incur in unexpected behaviors otherwise.
*
* @tparam Service Service type.
*/
template<typename Service>
class locator final {
class service_handle {
friend class locator<Service>;
std::shared_ptr<Service> value{};
};
public:
/*! @brief Service type. */
using type = Service;
/*! @brief Service node type. */
using node_type = service_handle;
/*! @brief Default constructor, deleted on purpose. */
locator() = delete;
/*! @brief Default copy constructor, deleted on purpose. */
locator(const locator &) = delete;
/*! @brief Default destructor, deleted on purpose. */
~locator() = delete;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This locator.
*/
locator &operator=(const locator &) = delete;
/**
* @brief Checks whether a service locator contains a value.
* @return True if the service locator contains a value, false otherwise.
*/
[[nodiscard]] static bool has_value() noexcept {
return (service != nullptr);
}
/**
* @brief Returns a reference to a valid service, if any.
*
* @warning
* Invoking this function can result in undefined behavior if the service
* hasn't been set yet.
*
* @return A reference to the service currently set, if any.
*/
[[nodiscard]] static Service &value() noexcept {
ENTT_ASSERT(has_value(), "Service not available");
return *service;
}
/**
* @brief Returns a service if available or sets it from a fallback type.
*
* Arguments are used only if a service doesn't already exist. In all other
* cases, they are discarded.
*
* @tparam Args Types of arguments to use to construct the fallback service.
* @tparam Type Fallback service type.
* @param args Parameters to use to construct the fallback service.
* @return A reference to a valid service.
*/
template<typename Type = Service, typename... Args>
[[nodiscard]] static Service &value_or(Args &&...args) {
return service ? *service : emplace<Type>(std::forward<Args>(args)...);
}
/**
* @brief Sets or replaces a service.
* @tparam Type Service type.
* @tparam Args Types of arguments to use to construct the service.
* @param args Parameters to use to construct the service.
* @return A reference to a valid service.
*/
template<typename Type = Service, typename... Args>
static Service &emplace(Args &&...args) {
service = std::make_shared<Type>(std::forward<Args>(args)...);
return *service;
}
/**
* @brief Sets or replaces a service using a given allocator.
* @tparam Type Service type.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the service.
* @param alloc The allocator to use.
* @param args Parameters to use to construct the service.
* @return A reference to a valid service.
*/
template<typename Type = Service, typename Allocator, typename... Args>
static Service &emplace(std::allocator_arg_t, Allocator alloc, Args &&...args) {
service = std::allocate_shared<Type>(alloc, std::forward<Args>(args)...);
return *service;
}
/**
* @brief Returns a handle to the underlying service.
* @return A handle to the underlying service.
*/
static node_type handle() noexcept {
node_type node{};
node.value = service;
return node;
}
/**
* @brief Resets or replaces a service.
* @param other Optional handle with which to replace the service.
*/
static void reset(const node_type &other = {}) noexcept {
service = other.value;
}
/**
* @brief Resets or replaces a service.
* @tparam Type Service type.
* @tparam Deleter Deleter type.
* @param elem A pointer to a service to manage.
* @param deleter A deleter to use to destroy the service.
*/
template<typename Type, typename Deleter = std::default_delete<Type>>
static void reset(Type *elem, Deleter deleter = {}) {
service = std::shared_ptr<Service>{elem, std::move(deleter)};
}
private:
// std::shared_ptr because of its type erased allocator which is useful here
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
inline static std::shared_ptr<Service> service{};
};
} // namespace entt
#endif

View File

@@ -0,0 +1,35 @@
#ifndef ENTT_META_ADL_POINTER_HPP
#define ENTT_META_ADL_POINTER_HPP
namespace entt {
/**
* @brief ADL based lookup function for dereferencing meta pointer-like types.
* @tparam Type Element type.
* @param value A pointer-like object.
* @return The value returned from the dereferenced pointer.
*/
template<typename Type>
decltype(auto) dereference_meta_pointer_like(const Type &value) {
return *value;
}
/**
* @brief Fake ADL based lookup function for meta pointer-like types.
* @tparam Type Element type.
*/
template<typename Type>
struct adl_meta_pointer_like {
/**
* @brief Uses the default ADL based lookup method to resolve the call.
* @param value A pointer-like object.
* @return The value returned from the dereferenced pointer.
*/
static decltype(auto) dereference(const Type &value) {
return dereference_meta_pointer_like(value);
}
};
} // namespace entt
#endif

View File

@@ -0,0 +1,388 @@
// IWYU pragma: always_keep
#ifndef ENTT_META_CONTAINER_HPP
#define ENTT_META_CONTAINER_HPP
#include <array>
#include <deque>
#include <iterator>
#include <list>
#include <map>
#include <set>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "../container/dense_map.hpp"
#include "../container/dense_set.hpp"
#include "context.hpp"
#include "meta.hpp"
#include "type_traits.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename, typename = void>
struct fixed_size_sequence_container: std::true_type {};
template<typename Type>
struct fixed_size_sequence_container<Type, std::void_t<decltype(&Type::clear)>>: std::false_type {};
template<typename Type>
inline constexpr bool fixed_size_sequence_container_v = fixed_size_sequence_container<Type>::value;
template<typename, typename = void>
struct key_only_associative_container: std::true_type {};
template<typename Type>
struct key_only_associative_container<Type, std::void_t<typename Type::mapped_type>>: std::false_type {};
template<typename Type>
inline constexpr bool key_only_associative_container_v = key_only_associative_container<Type>::value;
template<typename, typename = void>
struct reserve_aware_container: std::false_type {};
template<typename Type>
struct reserve_aware_container<Type, std::void_t<decltype(&Type::reserve)>>: std::true_type {};
template<typename Type>
inline constexpr bool reserve_aware_container_v = reserve_aware_container<Type>::value;
} // namespace internal
/*! @endcond */
/**
* @brief General purpose implementation of meta sequence container traits.
* @tparam Type Type of underlying sequence container.
*/
template<typename Type>
struct basic_meta_sequence_container_traits {
static_assert(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>, "Unexpected type");
/*! @brief Unsigned integer type. */
using size_type = typename meta_sequence_container::size_type;
/*! @brief Meta iterator type. */
using iterator = typename meta_sequence_container::iterator;
/*! @brief True in case of key-only containers, false otherwise. */
static constexpr bool fixed_size = internal::fixed_size_sequence_container_v<Type>;
/**
* @brief Returns the number of elements in a container.
* @param container Opaque pointer to a container of the given type.
* @return Number of elements.
*/
[[nodiscard]] static size_type size(const void *container) {
return static_cast<const Type *>(container)->size();
}
/**
* @brief Clears a container.
* @param container Opaque pointer to a container of the given type.
* @return True in case of success, false otherwise.
*/
[[nodiscard]] static bool clear([[maybe_unused]] void *container) {
if constexpr(fixed_size) {
return false;
} else {
static_cast<Type *>(container)->clear();
return true;
}
}
/**
* @brief Increases the capacity of a container.
* @param container Opaque pointer to a container of the given type.
* @param sz Desired capacity.
* @return True in case of success, false otherwise.
*/
[[nodiscard]] static bool reserve([[maybe_unused]] void *container, [[maybe_unused]] const size_type sz) {
if constexpr(internal::reserve_aware_container_v<Type>) {
static_cast<Type *>(container)->reserve(sz);
return true;
} else {
return false;
}
}
/**
* @brief Resizes a container.
* @param container Opaque pointer to a container of the given type.
* @param sz The new number of elements.
* @return True in case of success, false otherwise.
*/
[[nodiscard]] static bool resize([[maybe_unused]] void *container, [[maybe_unused]] const size_type sz) {
if constexpr(fixed_size || !std::is_default_constructible_v<typename Type::value_type>) {
return false;
} else {
static_cast<Type *>(container)->resize(sz);
return true;
}
}
/**
* @brief Returns a possibly const iterator to the beginning.
* @param area The context to pass to the newly created iterator.
* @param container Opaque pointer to a container of the given type.
* @param as_const Const opaque pointer fallback.
* @return An iterator to the first element of the container.
*/
static iterator begin(const meta_ctx &area, void *container, const void *as_const) {
return (container != nullptr) ? iterator{area, static_cast<Type *>(container)->begin()}
: iterator{area, static_cast<const Type *>(as_const)->begin()};
}
/**
* @brief Returns a possibly const iterator to the end.
* @param area The context to pass to the newly created iterator.
* @param container Opaque pointer to a container of the given type.
* @param as_const Const opaque pointer fallback.
* @return An iterator that is past the last element of the container.
*/
static iterator end(const meta_ctx &area, void *container, const void *as_const) {
return (container != nullptr) ? iterator{area, static_cast<Type *>(container)->end()}
: iterator{area, static_cast<const Type *>(as_const)->end()};
}
/**
* @brief Assigns one element to a container and constructs its object from
* a given opaque instance.
* @param area The context to pass to the newly created iterator.
* @param container Opaque pointer to a container of the given type.
* @param value Optional opaque instance of the object to construct (as
* value type).
* @param cref Optional opaque instance of the object to construct (as
* decayed const reference type).
* @param it Iterator before which the element will be inserted.
* @return A possibly invalid iterator to the inserted element.
*/
[[nodiscard]] static iterator insert([[maybe_unused]] const meta_ctx &area, [[maybe_unused]] void *container, [[maybe_unused]] const void *value, [[maybe_unused]] const void *cref, [[maybe_unused]] const iterator &it) {
if constexpr(fixed_size) {
return iterator{};
} else {
auto *const non_const = any_cast<typename Type::iterator>(&it.base());
return {area, static_cast<Type *>(container)->insert(
non_const ? *non_const : any_cast<const typename Type::const_iterator &>(it.base()),
(value != nullptr) ? *static_cast<const typename Type::value_type *>(value) : *static_cast<const std::remove_reference_t<typename Type::const_reference> *>(cref))};
}
}
/**
* @brief Erases an element from a container.
* @param area The context to pass to the newly created iterator.
* @param container Opaque pointer to a container of the given type.
* @param it An opaque iterator to the element to erase.
* @return A possibly invalid iterator following the last removed element.
*/
[[nodiscard]] static iterator erase([[maybe_unused]] const meta_ctx &area, [[maybe_unused]] void *container, [[maybe_unused]] const iterator &it) {
if constexpr(fixed_size) {
return iterator{};
} else {
auto *const non_const = any_cast<typename Type::iterator>(&it.base());
return {area, static_cast<Type *>(container)->erase(non_const ? *non_const : any_cast<const typename Type::const_iterator &>(it.base()))};
}
}
};
/**
* @brief General purpose implementation of meta associative container traits.
* @tparam Type Type of underlying associative container.
*/
template<typename Type>
struct basic_meta_associative_container_traits {
static_assert(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>, "Unexpected type");
/*! @brief Unsigned integer type. */
using size_type = typename meta_associative_container::size_type;
/*! @brief Meta iterator type. */
using iterator = typename meta_associative_container::iterator;
/*! @brief True in case of key-only containers, false otherwise. */
static constexpr bool key_only = internal::key_only_associative_container_v<Type>;
/**
* @brief Returns the number of elements in a container.
* @param container Opaque pointer to a container of the given type.
* @return Number of elements.
*/
[[nodiscard]] static size_type size(const void *container) {
return static_cast<const Type *>(container)->size();
}
/**
* @brief Clears a container.
* @param container Opaque pointer to a container of the given type.
* @return True in case of success, false otherwise.
*/
[[nodiscard]] static bool clear(void *container) {
static_cast<Type *>(container)->clear();
return true;
}
/**
* @brief Increases the capacity of a container.
* @param container Opaque pointer to a container of the given type.
* @param sz Desired capacity.
* @return True in case of success, false otherwise.
*/
[[nodiscard]] static bool reserve([[maybe_unused]] void *container, [[maybe_unused]] const size_type sz) {
if constexpr(internal::reserve_aware_container_v<Type>) {
static_cast<Type *>(container)->reserve(sz);
return true;
} else {
return false;
}
}
/**
* @brief Returns a possibly const iterator to the beginning.
* @param area The context to pass to the newly created iterator.
* @param container Opaque pointer to a container of the given type.
* @param as_const Const opaque pointer fallback.
* @return An iterator to the first element of the container.
*/
static iterator begin(const meta_ctx &area, void *container, const void *as_const) {
return (container != nullptr) ? iterator{area, std::bool_constant<key_only>{}, static_cast<Type *>(container)->begin()}
: iterator{area, std::bool_constant<key_only>{}, static_cast<const Type *>(as_const)->begin()};
}
/**
* @brief Returns a possibly const iterator to the end.
* @param area The context to pass to the newly created iterator.
* @param container Opaque pointer to a container of the given type.
* @param as_const Const opaque pointer fallback.
* @return An iterator that is past the last element of the container.
*/
static iterator end(const meta_ctx &area, void *container, const void *as_const) {
return (container != nullptr) ? iterator{area, std::bool_constant<key_only>{}, static_cast<Type *>(container)->end()}
: iterator{area, std::bool_constant<key_only>{}, static_cast<const Type *>(as_const)->end()};
}
/**
* @brief Inserts an element into a container, if the key does not exist.
* @param container Opaque pointer to a container of the given type.
* @param key An opaque key value of an element to insert.
* @param value Optional opaque value to insert (key-value containers).
* @return True if the insertion took place, false otherwise.
*/
[[nodiscard]] static bool insert(void *container, const void *key, [[maybe_unused]] const void *value) {
if constexpr(key_only) {
return static_cast<Type *>(container)->insert(*static_cast<const typename Type::key_type *>(key)).second;
} else {
return static_cast<Type *>(container)->emplace(*static_cast<const typename Type::key_type *>(key), *static_cast<const typename Type::mapped_type *>(value)).second;
}
}
/**
* @brief Removes an element from a container.
* @param container Opaque pointer to a container of the given type.
* @param key An opaque key value of an element to remove.
* @return Number of elements removed (either 0 or 1).
*/
[[nodiscard]] static size_type erase(void *container, const void *key) {
return static_cast<Type *>(container)->erase(*static_cast<const typename Type::key_type *>(key));
}
/**
* @brief Finds an element with a given key.
* @param area The context to pass to the newly created iterator.
* @param container Opaque pointer to a container of the given type.
* @param as_const Const opaque pointer fallback.
* @param key Opaque key value of an element to search for.
* @return An iterator to the element with the given key, if any.
*/
static iterator find(const meta_ctx &area, void *container, const void *as_const, const void *key) {
return (container != nullptr) ? iterator{area, std::bool_constant<key_only>{}, static_cast<Type *>(container)->find(*static_cast<const typename Type::key_type *>(key))}
: iterator{area, std::bool_constant<key_only>{}, static_cast<const Type *>(as_const)->find(*static_cast<const typename Type::key_type *>(key))};
}
};
/**
* @brief Meta sequence container traits for `std::vector`s of any type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_sequence_container_traits<std::vector<Args...>>
: basic_meta_sequence_container_traits<std::vector<Args...>> {};
/**
* @brief Meta sequence container traits for `std::array`s of any type.
* @tparam Type Template arguments for the container.
* @tparam N Template arguments for the container.
*/
template<typename Type, auto N>
struct meta_sequence_container_traits<std::array<Type, N>>
: basic_meta_sequence_container_traits<std::array<Type, N>> {};
/**
* @brief Meta sequence container traits for `std::list`s of any type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_sequence_container_traits<std::list<Args...>>
: basic_meta_sequence_container_traits<std::list<Args...>> {};
/**
* @brief Meta sequence container traits for `std::deque`s of any type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_sequence_container_traits<std::deque<Args...>>
: basic_meta_sequence_container_traits<std::deque<Args...>> {};
/**
* @brief Meta associative container traits for `std::map`s of any type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_associative_container_traits<std::map<Args...>>
: basic_meta_associative_container_traits<std::map<Args...>> {};
/**
* @brief Meta associative container traits for `std::unordered_map`s of any
* type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_associative_container_traits<std::unordered_map<Args...>>
: basic_meta_associative_container_traits<std::unordered_map<Args...>> {};
/**
* @brief Meta associative container traits for `std::set`s of any type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_associative_container_traits<std::set<Args...>>
: basic_meta_associative_container_traits<std::set<Args...>> {};
/**
* @brief Meta associative container traits for `std::unordered_set`s of any
* type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_associative_container_traits<std::unordered_set<Args...>>
: basic_meta_associative_container_traits<std::unordered_set<Args...>> {};
/**
* @brief Meta associative container traits for `dense_map`s of any type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_associative_container_traits<dense_map<Args...>>
: basic_meta_associative_container_traits<dense_map<Args...>> {};
/**
* @brief Meta associative container traits for `dense_set`s of any type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_associative_container_traits<dense_set<Args...>>
: basic_meta_associative_container_traits<dense_set<Args...>> {};
} // namespace entt
#endif

View File

@@ -0,0 +1,51 @@
#ifndef ENTT_META_CTX_HPP
#define ENTT_META_CTX_HPP
#include "../container/dense_map.hpp"
#include "../core/fwd.hpp"
#include "../core/utility.hpp"
namespace entt {
class meta_ctx;
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
struct meta_type_node;
struct meta_context {
dense_map<id_type, meta_type_node, identity> value;
[[nodiscard]] inline static meta_context &from(meta_ctx &ctx);
[[nodiscard]] inline static const meta_context &from(const meta_ctx &ctx);
};
} // namespace internal
/*! @endcond */
/*! @brief Disambiguation tag for constructors and the like. */
class meta_ctx_arg_t final {};
/*! @brief Constant of type meta_context_arg_t used to disambiguate calls. */
inline constexpr meta_ctx_arg_t meta_ctx_arg{};
/*! @brief Opaque meta context type. */
class meta_ctx: private internal::meta_context {
// attorney idiom like model to access the base class
friend struct internal::meta_context;
};
/*! @cond TURN_OFF_DOXYGEN */
[[nodiscard]] inline internal::meta_context &internal::meta_context::from(meta_ctx &ctx) {
return ctx;
}
[[nodiscard]] inline const internal::meta_context &internal::meta_context::from(const meta_ctx &ctx) {
return ctx;
}
/*! @endcond */
} // namespace entt
#endif

View File

@@ -0,0 +1,625 @@
#ifndef ENTT_META_FACTORY_HPP
#define ENTT_META_FACTORY_HPP
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/bit.hpp"
#include "../core/fwd.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "../locator/locator.hpp"
#include "context.hpp"
#include "meta.hpp"
#include "node.hpp"
#include "policy.hpp"
#include "range.hpp"
#include "resolve.hpp"
#include "utility.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
class basic_meta_factory {
using invoke_type = std::remove_pointer_t<decltype(meta_func_node::invoke)>;
auto *find_member_or_assert() {
auto *member = find_member<&meta_data_node::id>(details->data, bucket);
ENTT_ASSERT(member != nullptr, "Cannot find member");
return member;
}
auto *find_overload_or_assert() {
auto *overload = find_overload(find_member<&meta_func_node::id>(details->func, bucket), invoke);
ENTT_ASSERT(overload != nullptr, "Cannot find overload");
return overload;
}
void reset_bucket(const id_type id, invoke_type *const ref = nullptr) {
invoke = ref;
bucket = id;
}
protected:
void type(const id_type id) noexcept {
reset_bucket(parent);
auto &&elem = meta_context::from(*ctx).value[parent];
ENTT_ASSERT(elem.id == id || !resolve(*ctx, id), "Duplicate identifier");
elem.id = id;
}
template<typename Type>
void insert_or_assign(Type node) {
reset_bucket(parent);
if constexpr(std::is_same_v<Type, meta_base_node>) {
auto *member = find_member<&meta_base_node::type>(details->base, node.type);
member ? (*member = node) : details->base.emplace_back(node);
} else if constexpr(std::is_same_v<Type, meta_conv_node>) {
auto *member = find_member<&meta_conv_node::type>(details->conv, node.type);
member ? (*member = node) : details->conv.emplace_back(node);
} else {
static_assert(std::is_same_v<Type, meta_ctor_node>, "Unexpected type");
auto *member = find_member<&meta_ctor_node::id>(details->ctor, node.id);
member ? (*member = node) : details->ctor.emplace_back(node);
}
}
void dtor(meta_dtor_node node) {
reset_bucket(parent);
meta_context::from(*ctx).value[parent].dtor = node;
}
void data(meta_data_node node) {
reset_bucket(node.id);
if(auto *member = find_member<&meta_data_node::id>(details->data, node.id); member == nullptr) {
details->data.emplace_back(std::move(node));
} else if(member->set != node.set || member->get != node.get) {
*member = std::move(node);
}
}
void func(meta_func_node node) {
reset_bucket(node.id, node.invoke);
if(auto *member = find_member<&meta_func_node::id>(details->func, node.id); member == nullptr) {
details->func.emplace_back(std::move(node));
} else if(auto *overload = find_overload(member, node.invoke); overload == nullptr) {
while(member->next != nullptr) { member = member->next.get(); }
member->next = std::make_shared<meta_func_node>(std::move(node));
}
}
void traits(const meta_traits value) {
if(bucket == parent) {
meta_context::from(*ctx).value[bucket].traits |= value;
} else if(invoke == nullptr) {
find_member_or_assert()->traits |= value;
} else {
find_overload_or_assert()->traits |= value;
}
}
void custom(meta_custom_node node) {
if(bucket == parent) {
meta_context::from(*ctx).value[bucket].custom = std::move(node);
} else if(invoke == nullptr) {
find_member_or_assert()->custom = std::move(node);
} else {
find_overload_or_assert()->custom = std::move(node);
}
}
public:
basic_meta_factory(meta_ctx &area, meta_type_node node)
: ctx{&area},
parent{node.info->hash()},
bucket{parent},
details{node.details.get()} {
if(details == nullptr) {
node.details = std::make_shared<meta_type_descriptor>();
meta_context::from(*ctx).value[parent] = node;
details = node.details.get();
}
}
private:
meta_ctx *ctx{};
id_type parent{};
id_type bucket{};
invoke_type *invoke{};
meta_type_descriptor *details{};
};
} // namespace internal
/*! @endcond */
/**
* @brief Meta factory to be used for reflection purposes.
* @tparam Type Reflected type for which the factory was created.
*/
template<typename Type>
class meta_factory: private internal::basic_meta_factory {
using base_type = internal::basic_meta_factory;
template<typename Setter, auto Getter, typename Policy, std::size_t... Index>
[[deprecated("use variant types or conversion support")]]
void data(const id_type id, std::index_sequence<Index...>) noexcept {
using data_type = std::invoke_result_t<decltype(Getter), Type &>;
using args_type = type_list<typename meta_function_helper_t<Type, decltype(value_list_element_v<Index, Setter>)>::args_type...>;
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
base_type::data(
internal::meta_data_node{
id,
/* this is never static */
(std::is_member_object_pointer_v<decltype(value_list_element_v<Index, Setter>)> && ... && std::is_const_v<std::remove_reference_t<data_type>>) ? internal::meta_traits::is_const : internal::meta_traits::is_none,
Setter::size,
&internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>,
&meta_arg<type_list<type_list_element_t<static_cast<std::size_t>(type_list_element_t<Index, args_type>::size != 1u), type_list_element_t<Index, args_type>>...>>,
+[](meta_handle instance, meta_any value) { return (meta_setter<Type, value_list_element_v<Index, Setter>>(*instance.operator->(), value.as_ref()) || ...); },
&meta_getter<Type, Getter, Policy>});
}
public:
/*! @brief Default constructor. */
meta_factory() noexcept
: meta_factory{locator<meta_ctx>::value_or()} {}
/**
* @brief Context aware constructor.
* @param area The context into which to construct meta types.
*/
meta_factory(meta_ctx &area) noexcept
: internal::basic_meta_factory{area, internal::resolve<Type>(internal::meta_context::from(area))} {}
/**
* @brief Assigns a custom unique identifier to a meta type.
* @param id A custom unique identifier.
* @return A meta factory for the given type.
*/
meta_factory type(const id_type id) noexcept {
base_type::type(id);
return *this;
}
/**
* @brief Assigns a meta base to a meta type.
*
* A reflected base class must be a real base class of the reflected type.
*
* @tparam Base Type of the base class to assign to the meta type.
* @return A meta factory for the parent type.
*/
template<typename Base>
meta_factory base() noexcept {
static_assert(!std::is_same_v<Type, Base> && std::is_base_of_v<Base, Type>, "Invalid base type");
auto *const op = +[](const void *instance) noexcept { return static_cast<const void *>(static_cast<const Base *>(static_cast<const Type *>(instance))); };
base_type::insert_or_assign(internal::meta_base_node{type_id<Base>().hash(), &internal::resolve<Base>, op});
return *this;
}
/**
* @brief Assigns a meta conversion function to a meta type.
*
* Conversion functions can be either free functions or member
* functions.<br/>
* In case of free functions, they must accept a const reference to an
* instance of the parent type as an argument. In case of member functions,
* they should have no arguments at all.
*
* @tparam Candidate The actual function to use for the conversion.
* @return A meta factory for the parent type.
*/
template<auto Candidate>
auto conv() noexcept {
using conv_type = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Candidate), Type &>>>;
auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, std::invoke(Candidate, *static_cast<const Type *>(instance))); };
base_type::insert_or_assign(internal::meta_conv_node{type_id<conv_type>().hash(), op});
return *this;
}
/**
* @brief Assigns a meta conversion function to a meta type.
*
* The given type must be such that an instance of the reflected type can be
* converted to it.
*
* @tparam To Type of the conversion function to assign to the meta type.
* @return A meta factory for the parent type.
*/
template<typename To>
meta_factory conv() noexcept {
using conv_type = std::remove_cv_t<std::remove_reference_t<To>>;
auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, static_cast<To>(*static_cast<const Type *>(instance))); };
base_type::insert_or_assign(internal::meta_conv_node{type_id<conv_type>().hash(), op});
return *this;
}
/**
* @brief Assigns a meta constructor to a meta type.
*
* Both member functions and free function can be assigned to meta types in
* the role of constructors. All that is required is that they return an
* instance of the underlying type.<br/>
* From a client's point of view, nothing changes if a constructor of a meta
* type is a built-in one or not.
*
* @tparam Candidate The actual function to use as a constructor.
* @tparam Policy Optional policy (no policy set by default).
* @return A meta factory for the parent type.
*/
template<auto Candidate, typename Policy = as_is_t>
meta_factory ctor() noexcept {
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy");
static_assert(std::is_same_v<std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>, Type>, "The function doesn't return an object of the required type");
base_type::insert_or_assign(internal::meta_ctor_node{type_id<typename descriptor::args_type>().hash(), descriptor::args_type::size, &meta_arg<typename descriptor::args_type>, &meta_construct<Type, Candidate, Policy>});
return *this;
}
/**
* @brief Assigns a meta constructor to a meta type.
*
* A meta constructor is uniquely identified by the types of its arguments
* and is such that there exists an actual constructor of the underlying
* type that can be invoked with parameters whose types are those given.
*
* @tparam Args Types of arguments to use to construct an instance.
* @return A meta factory for the parent type.
*/
template<typename... Args>
meta_factory ctor() noexcept {
// default constructor is already implicitly generated, no need for redundancy
if constexpr(sizeof...(Args) != 0u) {
using descriptor = meta_function_helper_t<Type, Type (*)(Args...)>;
base_type::insert_or_assign(internal::meta_ctor_node{type_id<typename descriptor::args_type>().hash(), descriptor::args_type::size, &meta_arg<typename descriptor::args_type>, &meta_construct<Type, Args...>});
}
return *this;
}
/**
* @brief Assigns a meta destructor to a meta type.
*
* Both free functions and member functions can be assigned to meta types in
* the role of destructors.<br/>
* The signature of a free function should be identical to the following:
*
* @code{.cpp}
* void(Type &);
* @endcode
*
* Member functions should not take arguments instead.<br/>
* The purpose is to give users the ability to free up resources that
* require special treatment before an object is actually destroyed.
*
* @tparam Func The actual function to use as a destructor.
* @return A meta factory for the parent type.
*/
template<auto Func>
meta_factory dtor() noexcept {
static_assert(std::is_invocable_v<decltype(Func), Type &>, "The function doesn't accept an object of the type provided");
auto *const op = +[](void *instance) { std::invoke(Func, *static_cast<Type *>(instance)); };
base_type::dtor(internal::meta_dtor_node{op});
return *this;
}
/**
* @brief Assigns a meta data to a meta type.
*
* Both data members and static and global variables, as well as constants
* of any kind, can be assigned to a meta type.<br/>
* From a client's point of view, all the variables associated with the
* reflected object will appear as if they were part of the type itself.
*
* @tparam Data The actual variable to attach to the meta type.
* @tparam Policy Optional policy (no policy set by default).
* @param id Unique identifier.
* @return A meta factory for the parent type.
*/
template<auto Data, typename Policy = as_is_t>
meta_factory data(const id_type id) noexcept {
if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
using data_type = std::invoke_result_t<decltype(Data), Type &>;
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
base_type::data(
internal::meta_data_node{
id,
/* this is never static */
std::is_const_v<std::remove_reference_t<data_type>> ? internal::meta_traits::is_const : internal::meta_traits::is_none,
1u,
&internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>,
&meta_arg<type_list<std::remove_cv_t<std::remove_reference_t<data_type>>>>,
&meta_setter<Type, Data>,
&meta_getter<Type, Data, Policy>});
} else {
using data_type = std::remove_pointer_t<decltype(Data)>;
if constexpr(std::is_pointer_v<decltype(Data)>) {
static_assert(Policy::template value<decltype(*Data)>, "Invalid return type for the given policy");
} else {
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
}
base_type::data(
internal::meta_data_node{
id,
((!std::is_pointer_v<decltype(Data)> || std::is_const_v<data_type>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static,
1u,
&internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>,
&meta_arg<type_list<std::remove_cv_t<std::remove_reference_t<data_type>>>>,
&meta_setter<Type, Data>,
&meta_getter<Type, Data, Policy>});
}
return *this;
}
/**
* @brief Assigns a meta data to a meta type by means of its setter and
* getter.
*
* Setters and getters can be either free functions, member functions or a
* mix of them.<br/>
* In case of free functions, setters and getters must accept a reference to
* an instance of the parent type as their first argument. A setter has then
* an extra argument of a type convertible to that of the parameter to
* set.<br/>
* In case of member functions, getters have no arguments at all, while
* setters has an argument of a type convertible to that of the parameter to
* set.
*
* @tparam Setter The actual function to use as a setter.
* @tparam Getter The actual function to use as a getter.
* @tparam Policy Optional policy (no policy set by default).
* @param id Unique identifier.
* @return A meta factory for the parent type.
*/
template<auto Setter, auto Getter, typename Policy = as_is_t>
meta_factory data(const id_type id) noexcept {
using descriptor = meta_function_helper_t<Type, decltype(Getter)>;
static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy");
if constexpr(std::is_same_v<decltype(Setter), std::nullptr_t>) {
base_type::data(
internal::meta_data_node{
id,
/* this is never static */
internal::meta_traits::is_const,
0u,
&internal::resolve<std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>>,
&meta_arg<type_list<>>,
&meta_setter<Type, Setter>,
&meta_getter<Type, Getter, Policy>});
} else {
using args_type = typename meta_function_helper_t<Type, decltype(Setter)>::args_type;
base_type::data(
internal::meta_data_node{
id,
/* this is never static nor const */
internal::meta_traits::is_none,
1u,
&internal::resolve<std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>>,
&meta_arg<type_list<type_list_element_t<static_cast<std::size_t>(args_type::size != 1u), args_type>>>,
&meta_setter<Type, Setter>,
&meta_getter<Type, Getter, Policy>});
}
return *this;
}
/**
* @brief Assigns a meta data to a meta type by means of its setters and
* getter.
*
* Multi-setter support for meta data members. All setters are tried in the
* order of definition before returning to the caller.<br/>
* Setters can be either free functions, member functions or a mix of them
* and are provided via a `value_list` type.
*
* @sa data
*
* @tparam Setter The actual functions to use as setters.
* @tparam Getter The actual getter function.
* @tparam Policy Optional policy (no policy set by default).
* @param id Unique identifier.
* @return A meta factory for the parent type.
*/
template<typename Setter, auto Getter, typename Policy = as_is_t>
[[deprecated("use variant types or conversion support")]]
meta_factory data(const id_type id) noexcept {
data<Setter, Getter, Policy>(id, std::make_index_sequence<Setter::size>{});
return *this;
}
/**
* @brief Assigns a meta function to a meta type.
*
* Both member functions and free functions can be assigned to a meta
* type.<br/>
* From a client's point of view, all the functions associated with the
* reflected object will appear as if they were part of the type itself.
*
* @tparam Candidate The actual function to attach to the meta type.
* @tparam Policy Optional policy (no policy set by default).
* @param id Unique identifier.
* @return A meta factory for the parent type.
*/
template<auto Candidate, typename Policy = as_is_t>
meta_factory func(const id_type id) noexcept {
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy");
base_type::func(
internal::meta_func_node{
id,
(descriptor::is_const ? internal::meta_traits::is_const : internal::meta_traits::is_none) | (descriptor::is_static ? internal::meta_traits::is_static : internal::meta_traits::is_none),
descriptor::args_type::size,
&internal::resolve<std::conditional_t<std::is_same_v<Policy, as_void_t>, void, std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>>>,
&meta_arg<typename descriptor::args_type>,
&meta_invoke<Type, Candidate, Policy>});
return *this;
}
/**
* @brief Sets traits on the last created meta object.
*
* The assigned value must be an enum and intended as a bitmask.
*
* @tparam Value Type of the traits value.
* @param value Traits value.
* @return A meta factory for the parent type.
*/
template<typename Value>
meta_factory traits(const Value value) {
static_assert(std::is_enum_v<Value>, "Invalid enum type");
base_type::traits(internal::user_to_meta_traits(value));
return *this;
}
/**
* @brief Sets user defined data that will never be used by the library.
* @tparam Value Type of user defined data to store.
* @tparam Args Types of arguments to use to construct the user data.
* @param args Parameters to use to initialize the user data.
* @return A meta factory for the parent type.
*/
template<typename Value, typename... Args>
meta_factory custom(Args &&...args) {
base_type::custom(internal::meta_custom_node{type_id<Value>().hash(), std::make_shared<Value>(std::forward<Args>(args)...)});
return *this;
}
};
/**
* @brief Utility function to use for reflection.
*
* This is the point from which everything starts.<br/>
* By invoking this function with a type that is not yet reflected, a meta type
* is created to which it will be possible to attach meta objects through a
* dedicated factory.
*
* @tparam Type Type to reflect.
* @param ctx The context into which to construct meta types.
* @return A meta factory for the given type.
*/
template<typename Type>
[[nodiscard]] [[deprecated("use meta_factory directly instead")]] auto meta(meta_ctx &ctx) noexcept {
return meta_factory<Type>{ctx};
}
/**
* @brief Utility function to use for reflection.
*
* This is the point from which everything starts.<br/>
* By invoking this function with a type that is not yet reflected, a meta type
* is created to which it will be possible to attach meta objects through a
* dedicated factory.
*
* @tparam Type Type to reflect.
* @return A meta factory for the given type.
*/
template<typename Type>
[[nodiscard]] [[deprecated("use meta_factory directly instead")]] auto meta() noexcept {
return meta<Type>(locator<meta_ctx>::value_or());
}
/**
* @brief Resets a type and all its parts.
*
* Resets a type and all its data members, member functions and properties, as
* well as its constructors, destructors and conversion functions if any.<br/>
* Base classes aren't reset but the link between the two types is removed.
*
* The type is also removed from the set of searchable types.
*
* @param id Unique identifier.
* @param ctx The context from which to reset meta types.
*/
inline void meta_reset(meta_ctx &ctx, const id_type id) noexcept {
auto &&context = internal::meta_context::from(ctx);
for(auto it = context.value.begin(); it != context.value.end();) {
if(it->second.id == id) {
it = context.value.erase(it);
} else {
++it;
}
}
}
/**
* @brief Resets a type and all its parts.
*
* Resets a type and all its data members, member functions and properties, as
* well as its constructors, destructors and conversion functions if any.<br/>
* Base classes aren't reset but the link between the two types is removed.
*
* The type is also removed from the set of searchable types.
*
* @param id Unique identifier.
*/
inline void meta_reset(const id_type id) noexcept {
meta_reset(locator<meta_ctx>::value_or(), id);
}
/**
* @brief Resets a type and all its parts.
*
* @sa meta_reset
*
* @tparam Type Type to reset.
* @param ctx The context from which to reset meta types.
*/
template<typename Type>
void meta_reset(meta_ctx &ctx) noexcept {
internal::meta_context::from(ctx).value.erase(type_id<Type>().hash());
}
/**
* @brief Resets a type and all its parts.
*
* @sa meta_reset
*
* @tparam Type Type to reset.
*/
template<typename Type>
void meta_reset() noexcept {
meta_reset<Type>(locator<meta_ctx>::value_or());
}
/**
* @brief Resets all meta types.
*
* @sa meta_reset
*
* @param ctx The context from which to reset meta types.
*/
inline void meta_reset(meta_ctx &ctx) noexcept {
internal::meta_context::from(ctx).value.clear();
}
/**
* @brief Resets all meta types.
*
* @sa meta_reset
*/
inline void meta_reset() noexcept {
meta_reset(locator<meta_ctx>::value_or());
}
} // namespace entt
#endif

24
src/external/entt/src/entt/meta/fwd.hpp vendored Normal file
View File

@@ -0,0 +1,24 @@
#ifndef ENTT_META_FWD_HPP
#define ENTT_META_FWD_HPP
namespace entt {
class meta_sequence_container;
class meta_associative_container;
class meta_any;
struct meta_handle;
struct meta_custom;
struct meta_data;
struct meta_func;
class meta_type;
} // namespace entt
#endif

1956
src/external/entt/src/entt/meta/meta.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

324
src/external/entt/src/entt/meta/node.hpp vendored Normal file
View File

@@ -0,0 +1,324 @@
#ifndef ENTT_META_NODE_HPP
#define ENTT_META_NODE_HPP
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../core/attribute.h"
#include "../core/bit.hpp"
#include "../core/enum.hpp"
#include "../core/fwd.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "../core/utility.hpp"
#include "context.hpp"
#include "type_traits.hpp"
namespace entt {
class meta_any;
class meta_type;
struct meta_handle;
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
enum class meta_traits : std::uint32_t {
is_none = 0x0000,
is_const = 0x0001,
is_static = 0x0002,
is_arithmetic = 0x0004,
is_integral = 0x0008,
is_signed = 0x0010,
is_array = 0x0020,
is_enum = 0x0040,
is_class = 0x0080,
is_pointer = 0x0100,
is_pointer_like = 0x0200,
is_sequence_container = 0x0400,
is_associative_container = 0x0800,
_user_defined_traits = 0xFFFF,
_entt_enum_as_bitmask = 0xFFFF
};
template<typename Type>
[[nodiscard]] auto meta_to_user_traits(const meta_traits traits) noexcept {
static_assert(std::is_enum_v<Type>, "Invalid enum type");
constexpr auto shift = popcount(static_cast<std::underlying_type_t<meta_traits>>(meta_traits::_user_defined_traits));
return Type{static_cast<std::underlying_type_t<Type>>(static_cast<std::underlying_type_t<meta_traits>>(traits) >> shift)};
}
template<typename Type>
[[nodiscard]] auto user_to_meta_traits(const Type value) noexcept {
static_assert(std::is_enum_v<Type>, "Invalid enum type");
constexpr auto shift = popcount(static_cast<std::underlying_type_t<meta_traits>>(meta_traits::_user_defined_traits));
const auto traits = static_cast<std::underlying_type_t<internal::meta_traits>>(static_cast<std::underlying_type_t<Type>>(value));
ENTT_ASSERT(traits < ((~static_cast<std::underlying_type_t<meta_traits>>(meta_traits::_user_defined_traits)) >> shift), "Invalid traits");
return meta_traits{traits << shift};
}
struct meta_type_node;
struct meta_custom_node {
id_type type{};
std::shared_ptr<void> value;
};
struct meta_base_node {
id_type type{};
meta_type_node (*resolve)(const meta_context &) noexcept {};
const void *(*cast)(const void *) noexcept {};
};
struct meta_conv_node {
id_type type{};
meta_any (*conv)(const meta_ctx &, const void *){};
};
struct meta_ctor_node {
using size_type = std::size_t;
id_type id{};
size_type arity{0u};
meta_type (*arg)(const meta_ctx &, const size_type) noexcept {};
meta_any (*invoke)(const meta_ctx &, meta_any *const){};
};
struct meta_dtor_node {
void (*dtor)(void *){};
};
struct meta_data_node {
using size_type = std::size_t;
id_type id{};
meta_traits traits{meta_traits::is_none};
size_type arity{0u};
meta_type_node (*type)(const meta_context &) noexcept {};
meta_type (*arg)(const meta_ctx &, const size_type) noexcept {};
bool (*set)(meta_handle, meta_any){};
meta_any (*get)(meta_handle){};
meta_custom_node custom{};
};
struct meta_func_node {
using size_type = std::size_t;
id_type id{};
meta_traits traits{meta_traits::is_none};
size_type arity{0u};
meta_type_node (*ret)(const meta_context &) noexcept {};
meta_type (*arg)(const meta_ctx &, const size_type) noexcept {};
meta_any (*invoke)(meta_handle, meta_any *const){};
std::shared_ptr<meta_func_node> next;
meta_custom_node custom{};
};
struct meta_template_node {
using size_type = std::size_t;
size_type arity{0u};
meta_type_node (*resolve)(const meta_context &) noexcept {};
meta_type_node (*arg)(const meta_context &, const size_type) noexcept {};
};
struct meta_type_descriptor {
std::vector<meta_ctor_node> ctor;
std::vector<meta_base_node> base;
std::vector<meta_conv_node> conv;
std::vector<meta_data_node> data;
std::vector<meta_func_node> func;
};
struct meta_type_node {
using size_type = std::size_t;
const type_info *info{};
id_type id{};
meta_traits traits{meta_traits::is_none};
size_type size_of{0u};
meta_type_node (*resolve)(const meta_context &) noexcept {};
meta_type_node (*remove_pointer)(const meta_context &) noexcept {};
meta_any (*default_constructor)(const meta_ctx &){};
double (*conversion_helper)(void *, const void *){};
meta_any (*from_void)(const meta_ctx &, void *, const void *){};
meta_template_node templ{};
meta_dtor_node dtor{};
meta_custom_node custom{};
std::shared_ptr<meta_type_descriptor> details;
};
template<auto Member, typename Type, typename Value>
[[nodiscard]] auto *find_member(Type &from, const Value value) {
for(auto &&elem: from) {
if((elem.*Member) == value) {
return &elem;
}
}
return static_cast<typename Type::value_type *>(nullptr);
}
[[nodiscard]] inline auto *find_overload(meta_func_node *curr, std::remove_pointer_t<decltype(meta_func_node::invoke)> *const ref) {
while((curr != nullptr) && (curr->invoke != ref)) { curr = curr->next.get(); }
return curr;
}
template<auto Member>
[[nodiscard]] auto *look_for(const meta_context &context, const meta_type_node &node, const id_type id) {
using value_type = typename std::remove_reference_t<decltype((node.details.get()->*Member))>::value_type;
if(node.details) {
if(auto *member = find_member<&value_type::id>((node.details.get()->*Member), id); member != nullptr) {
return member;
}
for(auto &&curr: node.details->base) {
if(auto *elem = look_for<Member>(context, curr.resolve(context), id); elem) {
return elem;
}
}
}
return static_cast<value_type *>(nullptr);
}
template<typename Type>
meta_type_node resolve(const meta_context &) noexcept;
template<typename... Args>
[[nodiscard]] auto meta_arg_node(const meta_context &context, type_list<Args...>, [[maybe_unused]] const std::size_t index) noexcept {
meta_type_node (*value)(const meta_context &) noexcept = nullptr;
if constexpr(sizeof...(Args) != 0u) {
std::size_t pos{};
((value = (pos++ == index ? &resolve<std::remove_cv_t<std::remove_reference_t<Args>>> : value)), ...);
}
ENTT_ASSERT(value != nullptr, "Out of bounds");
return value(context);
}
[[nodiscard]] inline const void *try_cast(const meta_context &context, const meta_type_node &from, const type_info &to, const void *instance) noexcept {
if((from.info != nullptr) && *from.info == to) {
return instance;
}
if(from.details) {
for(auto &&curr: from.details->base) {
if(const void *elem = try_cast(context, curr.resolve(context), to, curr.cast(instance)); elem) {
return elem;
}
}
}
return nullptr;
}
template<typename Func>
[[nodiscard]] inline auto try_convert(const meta_context &context, const meta_type_node &from, const type_info &to, const bool arithmetic_or_enum, const void *instance, Func func) {
if(from.info && *from.info == to) {
return func(instance, from);
}
if(from.details) {
for(auto &&elem: from.details->conv) {
if(elem.type == to.hash()) {
return func(instance, elem);
}
}
for(auto &&curr: from.details->base) {
if(auto other = try_convert(context, curr.resolve(context), to, arithmetic_or_enum, curr.cast(instance), func); other) {
return other;
}
}
}
if(from.conversion_helper && arithmetic_or_enum) {
return func(instance, from.conversion_helper);
}
return func(instance);
}
[[nodiscard]] inline const meta_type_node *try_resolve(const meta_context &context, const type_info &info) noexcept {
const auto it = context.value.find(info.hash());
return it != context.value.end() ? &it->second : nullptr;
}
template<typename Type>
[[nodiscard]] meta_type_node resolve(const meta_context &context) noexcept {
static_assert(std::is_same_v<Type, std::remove_const_t<std::remove_reference_t<Type>>>, "Invalid type");
if(auto *elem = try_resolve(context, type_id<Type>()); elem) {
return *elem;
}
meta_type_node node{
&type_id<Type>(),
type_id<Type>().hash(),
(std::is_arithmetic_v<Type> ? meta_traits::is_arithmetic : meta_traits::is_none)
| (std::is_integral_v<Type> ? meta_traits::is_integral : meta_traits::is_none)
| (std::is_signed_v<Type> ? meta_traits::is_signed : meta_traits::is_none)
| (std::is_array_v<Type> ? meta_traits::is_array : meta_traits::is_none)
| (std::is_enum_v<Type> ? meta_traits::is_enum : meta_traits::is_none)
| (std::is_class_v<Type> ? meta_traits::is_class : meta_traits::is_none)
| (std::is_pointer_v<Type> ? meta_traits::is_pointer : meta_traits::is_none)
| (is_meta_pointer_like_v<Type> ? meta_traits::is_pointer_like : meta_traits::is_none)
| (is_complete_v<meta_sequence_container_traits<Type>> ? meta_traits::is_sequence_container : meta_traits::is_none)
| (is_complete_v<meta_associative_container_traits<Type>> ? meta_traits::is_associative_container : meta_traits::is_none),
size_of_v<Type>,
&resolve<Type>,
&resolve<std::remove_cv_t<std::remove_pointer_t<Type>>>};
if constexpr(std::is_default_constructible_v<Type>) {
node.default_constructor = +[](const meta_ctx &ctx) {
return meta_any{ctx, std::in_place_type<Type>};
};
}
if constexpr(std::is_arithmetic_v<Type>) {
node.conversion_helper = +[](void *lhs, const void *rhs) {
return lhs ? static_cast<double>(*static_cast<Type *>(lhs) = static_cast<Type>(*static_cast<const double *>(rhs))) : static_cast<double>(*static_cast<const Type *>(rhs));
};
} else if constexpr(std::is_enum_v<Type>) {
node.conversion_helper = +[](void *lhs, const void *rhs) {
return lhs ? static_cast<double>(*static_cast<Type *>(lhs) = static_cast<Type>(static_cast<std::underlying_type_t<Type>>(*static_cast<const double *>(rhs)))) : static_cast<double>(*static_cast<const Type *>(rhs));
};
}
if constexpr(!std::is_void_v<Type> && !std::is_function_v<Type>) {
node.from_void = +[](const meta_ctx &ctx, void *elem, const void *celem) {
if(elem && celem) { // ownership construction request
return meta_any{ctx, std::in_place, static_cast<std::decay_t<Type> *>(elem)};
}
if(elem) { // non-const reference construction request
return meta_any{ctx, std::in_place_type<std::decay_t<Type> &>, *static_cast<std::decay_t<Type> *>(elem)};
}
// const reference construction request
return meta_any{ctx, std::in_place_type<const std::decay_t<Type> &>, *static_cast<const std::decay_t<Type> *>(celem)};
};
}
if constexpr(is_complete_v<meta_template_traits<Type>>) {
node.templ = meta_template_node{
meta_template_traits<Type>::args_type::size,
&resolve<typename meta_template_traits<Type>::class_type>,
+[](const meta_context &area, const std::size_t index) noexcept { return meta_arg_node(area, typename meta_template_traits<Type>::args_type{}, index); }};
}
return node;
}
} // namespace internal
/*! @endcond */
} // namespace entt
#endif

View File

@@ -0,0 +1,59 @@
// IWYU pragma: always_keep
#ifndef ENTT_META_POINTER_HPP
#define ENTT_META_POINTER_HPP
#include <memory>
#include <type_traits>
#include "type_traits.hpp"
namespace entt {
/**
* @brief Makes plain pointers pointer-like types for the meta system.
* @tparam Type Element type.
*/
template<typename Type>
struct is_meta_pointer_like<Type *>
: std::true_type {};
/**
* @brief Partial specialization used to reject pointers to arrays.
* @tparam Type Type of elements of the array.
* @tparam N Number of elements of the array.
*/
template<typename Type, std::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
struct is_meta_pointer_like<Type (*)[N]>
: std::false_type {};
/**
* @brief Makes `std::shared_ptr`s of any type pointer-like types for the meta
* system.
* @tparam Type Element type.
*/
template<typename Type>
struct is_meta_pointer_like<std::shared_ptr<Type>>
: std::true_type {};
/**
* @brief Makes `std::unique_ptr`s of any type pointer-like types for the meta
* system.
* @tparam Type Element type.
* @tparam Args Other arguments.
*/
template<typename Type, typename... Args>
struct is_meta_pointer_like<std::unique_ptr<Type, Args...>>
: std::true_type {};
/**
* @brief Specialization for self-proclaimed meta pointer like types.
* @tparam Type Element type.
*/
template<typename Type>
struct is_meta_pointer_like<Type, std::void_t<typename Type::is_meta_pointer_like>>
: std::true_type {};
} // namespace entt
#endif

View File

@@ -0,0 +1,58 @@
#ifndef ENTT_META_POLICY_HPP
#define ENTT_META_POLICY_HPP
#include <type_traits>
namespace entt {
/*! @brief Empty class type used to request the _as ref_ policy. */
struct as_ref_t final {
/*! @cond TURN_OFF_DOXYGEN */
template<typename Type>
static constexpr bool value = std::is_reference_v<Type> && !std::is_const_v<std::remove_reference_t<Type>>;
/*! @endcond */
};
/*! @brief Empty class type used to request the _as cref_ policy. */
struct as_cref_t final {
/*! @cond TURN_OFF_DOXYGEN */
template<typename Type>
static constexpr bool value = std::is_reference_v<Type>;
/*! @endcond */
};
/*! @brief Empty class type used to request the _as-is_ policy. */
struct as_is_t final {
/*! @cond TURN_OFF_DOXYGEN */
template<typename>
static constexpr bool value = true;
/*! @endcond */
};
/*! @brief Empty class type used to request the _as void_ policy. */
struct as_void_t final {
/*! @cond TURN_OFF_DOXYGEN */
template<typename>
static constexpr bool value = true;
/*! @endcond */
};
/**
* @brief Provides the member constant `value` to true if a type also is a meta
* policy, false otherwise.
* @tparam Type Type to check.
*/
template<typename Type>
struct is_meta_policy
: std::bool_constant<std::is_same_v<Type, as_ref_t> || std::is_same_v<Type, as_cref_t> || std::is_same_v<Type, as_is_t> || std::is_same_v<Type, as_void_t>> {};
/**
* @brief Helper variable template.
* @tparam Type Type to check.
*/
template<typename Type>
inline constexpr bool is_meta_policy_v = is_meta_policy<Type>::value;
} // namespace entt
#endif

View File

@@ -0,0 +1,151 @@
#ifndef ENTT_META_RANGE_HPP
#define ENTT_META_RANGE_HPP
#include <cstddef>
#include <iterator>
#include <utility>
#include "../core/fwd.hpp"
#include "../core/iterator.hpp"
#include "context.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
struct meta_base_node;
template<typename Type, typename It>
struct meta_range_iterator final {
using value_type = std::pair<id_type, Type>;
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
using iterator_concept = std::random_access_iterator_tag;
constexpr meta_range_iterator() noexcept
: it{},
ctx{} {}
constexpr meta_range_iterator(const meta_ctx &area, const It iter) noexcept
: it{iter},
ctx{&area} {}
constexpr meta_range_iterator &operator++() noexcept {
return ++it, *this;
}
constexpr meta_range_iterator operator++(int) noexcept {
const meta_range_iterator orig = *this;
return ++(*this), orig;
}
constexpr meta_range_iterator &operator--() noexcept {
return --it, *this;
}
constexpr meta_range_iterator operator--(int) noexcept {
const meta_range_iterator orig = *this;
return operator--(), orig;
}
constexpr meta_range_iterator &operator+=(const difference_type value) noexcept {
it += value;
return *this;
}
constexpr meta_range_iterator operator+(const difference_type value) const noexcept {
meta_range_iterator copy = *this;
return (copy += value);
}
constexpr meta_range_iterator &operator-=(const difference_type value) noexcept {
return (*this += -value);
}
constexpr meta_range_iterator operator-(const difference_type value) const noexcept {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
if constexpr(std::is_same_v<It, typename decltype(meta_context::value)::const_iterator>) {
return {it[value].first, Type{*ctx, it[value].second}};
} else if constexpr(std::is_same_v<typename std::iterator_traits<It>::value_type, meta_base_node>) {
return {it[value].type, Type{*ctx, it[value]}};
} else {
return {it[value].id, Type{*ctx, it[value]}};
}
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return operator*();
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return operator[](0);
}
template<typename... Args>
friend constexpr std::ptrdiff_t operator-(const meta_range_iterator<Args...> &, const meta_range_iterator<Args...> &) noexcept;
template<typename... Args>
friend constexpr bool operator==(const meta_range_iterator<Args...> &, const meta_range_iterator<Args...> &) noexcept;
template<typename... Args>
friend constexpr bool operator<(const meta_range_iterator<Args...> &, const meta_range_iterator<Args...> &) noexcept;
private:
It it;
const meta_ctx *ctx;
};
template<typename... Args>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return lhs.it - rhs.it;
}
template<typename... Args>
[[nodiscard]] constexpr bool operator==(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return lhs.it == rhs.it;
}
template<typename... Args>
[[nodiscard]] constexpr bool operator!=(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return !(lhs == rhs);
}
template<typename... Args>
[[nodiscard]] constexpr bool operator<(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return lhs.it < rhs.it;
}
template<typename... Args>
[[nodiscard]] constexpr bool operator>(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return rhs < lhs;
}
template<typename... Args>
[[nodiscard]] constexpr bool operator<=(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return !(lhs > rhs);
}
template<typename... Args>
[[nodiscard]] constexpr bool operator>=(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return !(lhs < rhs);
}
} // namespace internal
/*! @endcond */
/**
* @brief Iterable range to use to iterate all types of meta objects.
* @tparam Type Type of meta objects returned.
* @tparam It Type of forward iterator.
*/
template<typename Type, typename It>
using meta_range = iterable_adaptor<internal::meta_range_iterator<Type, It>>;
} // namespace entt
#endif

View File

@@ -0,0 +1,102 @@
#ifndef ENTT_META_RESOLVE_HPP
#define ENTT_META_RESOLVE_HPP
#include <type_traits>
#include "../core/type_info.hpp"
#include "../locator/locator.hpp"
#include "context.hpp"
#include "meta.hpp"
#include "node.hpp"
#include "range.hpp"
namespace entt {
/**
* @brief Returns the meta type associated with a given type.
* @tparam Type Type to use to search for a meta type.
* @param ctx The context from which to search for meta types.
* @return The meta type associated with the given type, if any.
*/
template<typename Type>
[[nodiscard]] meta_type resolve(const meta_ctx &ctx) noexcept {
auto &&context = internal::meta_context::from(ctx);
return {ctx, internal::resolve<std::remove_cv_t<std::remove_reference_t<Type>>>(context)};
}
/**
* @brief Returns the meta type associated with a given type.
* @tparam Type Type to use to search for a meta type.
* @return The meta type associated with the given type, if any.
*/
template<typename Type>
[[nodiscard]] meta_type resolve() noexcept {
return resolve<Type>(locator<meta_ctx>::value_or());
}
/**
* @brief Returns a range to use to visit all meta types.
* @param ctx The context from which to search for meta types.
* @return An iterable range to use to visit all meta types.
*/
[[nodiscard]] inline meta_range<meta_type, typename decltype(internal::meta_context::value)::const_iterator> resolve(const meta_ctx &ctx) noexcept {
auto &&context = internal::meta_context::from(ctx);
return {{ctx, context.value.cbegin()}, {ctx, context.value.cend()}};
}
/**
* @brief Returns a range to use to visit all meta types.
* @return An iterable range to use to visit all meta types.
*/
[[nodiscard]] inline meta_range<meta_type, typename decltype(internal::meta_context::value)::const_iterator> resolve() noexcept {
return resolve(locator<meta_ctx>::value_or());
}
/**
* @brief Returns the meta type associated with a given identifier, if any.
* @param ctx The context from which to search for meta types.
* @param id Unique identifier.
* @return The meta type associated with the given identifier, if any.
*/
[[nodiscard]] inline meta_type resolve(const meta_ctx &ctx, const id_type id) noexcept {
for(auto &&curr: resolve(ctx)) {
if(curr.second.id() == id) {
return curr.second;
}
}
return meta_type{};
}
/**
* @brief Returns the meta type associated with a given identifier, if any.
* @param id Unique identifier.
* @return The meta type associated with the given identifier, if any.
*/
[[nodiscard]] inline meta_type resolve(const id_type id) noexcept {
return resolve(locator<meta_ctx>::value_or(), id);
}
/**
* @brief Returns the meta type associated with a given type info object.
* @param ctx The context from which to search for meta types.
* @param info The type info object of the requested type.
* @return The meta type associated with the given type info object, if any.
*/
[[nodiscard]] inline meta_type resolve(const meta_ctx &ctx, const type_info &info) noexcept {
auto &&context = internal::meta_context::from(ctx);
const auto *elem = internal::try_resolve(context, info);
return (elem != nullptr) ? meta_type{ctx, *elem} : meta_type{};
}
/**
* @brief Returns the meta type associated with a given type info object.
* @param info The type info object of the requested type.
* @return The meta type associated with the given type info object, if any.
*/
[[nodiscard]] inline meta_type resolve(const type_info &info) noexcept {
return resolve(locator<meta_ctx>::value_or(), info);
}
} // namespace entt
#endif

View File

@@ -0,0 +1,29 @@
// IWYU pragma: always_keep
#ifndef ENTT_META_TEMPLATE_HPP
#define ENTT_META_TEMPLATE_HPP
#include "../core/type_traits.hpp"
namespace entt {
/*! @brief Utility class to disambiguate class templates. */
template<template<typename...> class>
struct meta_class_template_tag {};
/**
* @brief General purpose traits class for generating meta template information.
* @tparam Clazz Type of class template.
* @tparam Args Types of template arguments.
*/
template<template<typename...> class Clazz, typename... Args>
struct meta_template_traits<Clazz<Args...>> {
/*! @brief Wrapped class template. */
using class_type = meta_class_template_tag<Clazz>;
/*! @brief List of template arguments. */
using args_type = type_list<Args...>;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,54 @@
#ifndef ENTT_META_TYPE_TRAITS_HPP
#define ENTT_META_TYPE_TRAITS_HPP
#include <type_traits>
#include <utility>
namespace entt {
/**
* @brief Traits class template to be specialized to enable support for meta
* template information.
*/
template<typename>
struct meta_template_traits;
/**
* @brief Traits class template to be specialized to enable support for meta
* sequence containers.
*/
template<typename>
struct meta_sequence_container_traits;
/**
* @brief Traits class template to be specialized to enable support for meta
* associative containers.
*/
template<typename>
struct meta_associative_container_traits;
/**
* @brief Provides the member constant `value` to true if a given type is a
* pointer-like type from the point of view of the meta system, false otherwise.
*/
template<typename, typename = void>
struct is_meta_pointer_like: std::false_type {};
/**
* @brief Partial specialization to ensure that const pointer-like types are
* also accepted.
* @tparam Type Potentially pointer-like type.
*/
template<typename Type>
struct is_meta_pointer_like<const Type>: is_meta_pointer_like<Type> {};
/**
* @brief Helper variable template.
* @tparam Type Potentially pointer-like type.
*/
template<typename Type>
inline constexpr auto is_meta_pointer_like_v = is_meta_pointer_like<Type>::value;
} // namespace entt
#endif

View File

@@ -0,0 +1,523 @@
#ifndef ENTT_META_UTILITY_HPP
#define ENTT_META_UTILITY_HPP
#include <cstddef>
#include <functional>
#include <type_traits>
#include <utility>
#include "../core/type_traits.hpp"
#include "../locator/locator.hpp"
#include "meta.hpp"
#include "node.hpp"
#include "policy.hpp"
namespace entt {
/**
* @brief Meta function descriptor traits.
* @tparam Ret Function return type.
* @tparam Args Function arguments.
* @tparam Static Function staticness.
* @tparam Const Function constness.
*/
template<typename Ret, typename Args, bool Static, bool Const>
struct meta_function_descriptor_traits {
/*! @brief Meta function return type. */
using return_type = Ret;
/*! @brief Meta function arguments. */
using args_type = Args;
/*! @brief True if the meta function is static, false otherwise. */
static constexpr bool is_static = Static;
/*! @brief True if the meta function is const, false otherwise. */
static constexpr bool is_const = Const;
};
/*! @brief Primary template isn't defined on purpose. */
template<typename, typename>
struct meta_function_descriptor;
/**
* @brief Meta function descriptor.
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Ret Function return type.
* @tparam Class Actual owner of the member function.
* @tparam Args Function arguments.
*/
template<typename Type, typename Ret, typename Class, typename... Args>
struct meta_function_descriptor<Type, Ret (Class::*)(Args...) const>
: meta_function_descriptor_traits<
Ret,
std::conditional_t<std::is_base_of_v<Class, Type>, type_list<Args...>, type_list<const Class &, Args...>>,
!std::is_base_of_v<Class, Type>,
true> {};
/**
* @brief Meta function descriptor.
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Ret Function return type.
* @tparam Class Actual owner of the member function.
* @tparam Args Function arguments.
*/
template<typename Type, typename Ret, typename Class, typename... Args>
struct meta_function_descriptor<Type, Ret (Class::*)(Args...)>
: meta_function_descriptor_traits<
Ret,
std::conditional_t<std::is_base_of_v<Class, Type>, type_list<Args...>, type_list<Class &, Args...>>,
!std::is_base_of_v<Class, Type>,
false> {};
/**
* @brief Meta function descriptor.
* @tparam Type Reflected type to which the meta data is associated.
* @tparam Class Actual owner of the data member.
* @tparam Ret Data member type.
*/
template<typename Type, typename Ret, typename Class>
struct meta_function_descriptor<Type, Ret Class::*>
: meta_function_descriptor_traits<
Ret &,
std::conditional_t<std::is_base_of_v<Class, Type>, type_list<>, type_list<Class &>>,
!std::is_base_of_v<Class, Type>,
false> {};
/**
* @brief Meta function descriptor.
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Ret Function return type.
* @tparam MaybeType First function argument.
* @tparam Args Other function arguments.
*/
template<typename Type, typename Ret, typename MaybeType, typename... Args>
struct meta_function_descriptor<Type, Ret (*)(MaybeType, Args...)>
: meta_function_descriptor_traits<
Ret,
std::conditional_t<
std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>,
type_list<Args...>,
type_list<MaybeType, Args...>>,
!(std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>),
std::is_const_v<std::remove_reference_t<MaybeType>> && (std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>)> {};
/**
* @brief Meta function descriptor.
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Ret Function return type.
*/
template<typename Type, typename Ret>
struct meta_function_descriptor<Type, Ret (*)()>
: meta_function_descriptor_traits<
Ret,
type_list<>,
true,
false> {};
/**
* @brief Meta function helper.
*
* Converts a function type to be associated with a reflected type into its meta
* function descriptor.
*
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Candidate The actual function to associate with the reflected type.
*/
template<typename Type, typename Candidate>
class meta_function_helper {
template<typename Ret, typename... Args, typename Class>
static constexpr meta_function_descriptor<Type, Ret (Class::*)(Args...) const> get_rid_of_noexcept(Ret (Class::*)(Args...) const);
template<typename Ret, typename... Args, typename Class>
static constexpr meta_function_descriptor<Type, Ret (Class::*)(Args...)> get_rid_of_noexcept(Ret (Class::*)(Args...));
template<typename Ret, typename Class, typename = std::enable_if_t<std::is_member_object_pointer_v<Ret Class::*>>>
static constexpr meta_function_descriptor<Type, Ret Class::*> get_rid_of_noexcept(Ret Class::*);
template<typename Ret, typename... Args>
static constexpr meta_function_descriptor<Type, Ret (*)(Args...)> get_rid_of_noexcept(Ret (*)(Args...));
template<typename Class>
static constexpr meta_function_descriptor<Class, decltype(&Class::operator())> get_rid_of_noexcept(Class);
public:
/*! @brief The meta function descriptor of the given function. */
using type = decltype(get_rid_of_noexcept(std::declval<Candidate>()));
};
/**
* @brief Helper type.
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Candidate The actual function to associate with the reflected type.
*/
template<typename Type, typename Candidate>
using meta_function_helper_t = typename meta_function_helper<Type, Candidate>::type;
/**
* @brief Wraps a value depending on the given policy.
*
* This function always returns a wrapped value in the requested context.<br/>
* Therefore, if the passed value is itself a wrapped object with a different
* context, it undergoes a rebinding to the requested context.
*
* @tparam Policy Optional policy (no policy set by default).
* @tparam Type Type of value to wrap.
* @param ctx The context from which to search for meta types.
* @param value Value to wrap.
* @return A meta any containing the returned value, if any.
*/
template<typename Policy = as_is_t, typename Type>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) {
if constexpr(std::is_same_v<Policy, as_void_t>) {
return meta_any{ctx, std::in_place_type<void>};
} else if constexpr(std::is_same_v<Policy, as_ref_t>) {
return meta_any{ctx, std::in_place_type<Type>, value};
} else if constexpr(std::is_same_v<Policy, as_cref_t>) {
static_assert(std::is_lvalue_reference_v<Type>, "Invalid type");
return meta_any{ctx, std::in_place_type<const std::remove_reference_t<Type> &>, std::as_const(value)};
} else {
return meta_any{ctx, std::forward<Type>(value)};
}
}
/**
* @brief Wraps a value depending on the given policy.
* @tparam Policy Optional policy (no policy set by default).
* @tparam Type Type of value to wrap.
* @param value Value to wrap.
* @return A meta any containing the returned value, if any.
*/
template<typename Policy = as_is_t, typename Type>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(Type &&value) {
return meta_dispatch<Policy, Type>(locator<meta_ctx>::value_or(), std::forward<Type>(value));
}
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Policy, typename Candidate, typename... Args>
[[nodiscard]] meta_any meta_invoke_with_args(const meta_ctx &ctx, Candidate &&candidate, Args &&...args) {
if constexpr(std::is_void_v<decltype(std::invoke(std::forward<Candidate>(candidate), args...))>) {
std::invoke(std::forward<Candidate>(candidate), args...);
return meta_any{ctx, std::in_place_type<void>};
} else {
return meta_dispatch<Policy>(ctx, std::invoke(std::forward<Candidate>(candidate), args...));
}
}
template<typename Type, typename Policy, typename Candidate, std::size_t... Index>
[[nodiscard]] meta_any meta_invoke(meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *const args, std::index_sequence<Index...>) {
using descriptor = meta_function_helper_t<Type, std::remove_reference_t<Candidate>>;
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) - waiting for C++20 (and std::span)
if constexpr(std::is_invocable_v<std::remove_reference_t<Candidate>, const Type &, type_list_element_t<Index, typename descriptor::args_type>...>) {
if(const auto *const clazz = instance->try_cast<const Type>(); clazz && ((args + Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
return meta_invoke_with_args<Policy>(instance->context(), std::forward<Candidate>(candidate), *clazz, (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
}
} else if constexpr(std::is_invocable_v<std::remove_reference_t<Candidate>, Type &, type_list_element_t<Index, typename descriptor::args_type>...>) {
if(auto *const clazz = instance->try_cast<Type>(); clazz && ((args + Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
return meta_invoke_with_args<Policy>(instance->context(), std::forward<Candidate>(candidate), *clazz, (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
}
} else {
if(((args + Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
return meta_invoke_with_args<Policy>(instance->context(), std::forward<Candidate>(candidate), (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
}
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
return meta_any{meta_ctx_arg, instance->context()};
}
template<typename Type, typename... Args, std::size_t... Index>
[[nodiscard]] meta_any meta_construct(const meta_ctx &ctx, meta_any *const args, std::index_sequence<Index...>) {
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) - waiting for C++20 (and std::span)
if(((args + Index)->allow_cast<Args>() && ...)) {
return meta_any{ctx, std::in_place_type<Type>, (args + Index)->cast<Args>()...};
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
return meta_any{meta_ctx_arg, ctx};
}
} // namespace internal
/*! @endcond */
/**
* @brief Returns the meta type of the i-th element of a list of arguments.
* @tparam Type Type list of the actual types of arguments.
* @param ctx The context from which to search for meta types.
* @param index The index of the element for which to return the meta type.
* @return The meta type of the i-th element of the list of arguments.
*/
template<typename Type>
[[nodiscard]] static meta_type meta_arg(const meta_ctx &ctx, const std::size_t index) noexcept {
auto &&context = internal::meta_context::from(ctx);
return {ctx, internal::meta_arg_node(context, Type{}, index)};
}
/**
* @brief Returns the meta type of the i-th element of a list of arguments.
* @tparam Type Type list of the actual types of arguments.
* @param index The index of the element for which to return the meta type.
* @return The meta type of the i-th element of the list of arguments.
*/
template<typename Type>
[[nodiscard]] static meta_type meta_arg(const std::size_t index) noexcept {
return meta_arg<Type>(locator<meta_ctx>::value_or(), index);
}
/**
* @brief Sets the value of a given variable.
* @tparam Type Reflected type to which the variable is associated.
* @tparam Data The actual variable to set.
* @param instance An opaque instance of the underlying type, if required.
* @param value Parameter to use to set the variable.
* @return True in case of success, false otherwise.
*/
template<typename Type, auto Data>
[[nodiscard]] bool meta_setter([[maybe_unused]] meta_handle instance, [[maybe_unused]] meta_any value) {
if constexpr(std::is_member_function_pointer_v<decltype(Data)> || std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>>) {
using descriptor = meta_function_helper_t<Type, decltype(Data)>;
using data_type = type_list_element_t<descriptor::is_static, typename descriptor::args_type>;
if(auto *const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
std::invoke(Data, *clazz, value.cast<data_type>());
return true;
}
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
using data_type = std::remove_reference_t<typename meta_function_helper_t<Type, decltype(Data)>::return_type>;
if constexpr(!std::is_array_v<data_type> && !std::is_const_v<data_type>) {
if(auto *const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
std::invoke(Data, *clazz) = value.cast<data_type>();
return true;
}
}
} else if constexpr(std::is_pointer_v<decltype(Data)>) {
using data_type = std::remove_reference_t<decltype(*Data)>;
if constexpr(!std::is_array_v<data_type> && !std::is_const_v<data_type>) {
if(value.allow_cast<data_type>()) {
*Data = value.cast<data_type>();
return true;
}
}
}
return false;
}
/**
* @brief Gets the value of a given variable.
* @tparam Type Reflected type to which the variable is associated.
* @tparam Data The actual variable to get.
* @tparam Policy Optional policy (no policy set by default).
* @param instance An opaque instance of the underlying type, if required.
* @return A meta any containing the value of the underlying variable.
*/
template<typename Type, auto Data, typename Policy = as_is_t>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_getter(meta_handle instance) {
if constexpr(std::is_member_pointer_v<decltype(Data)> || std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>>) {
if constexpr(!std::is_array_v<std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Data), Type &>>>>) {
if constexpr(std::is_invocable_v<decltype(Data), Type &>) {
if(auto *clazz = instance->try_cast<Type>(); clazz) {
return meta_dispatch<Policy>(instance->context(), std::invoke(Data, *clazz));
}
}
if constexpr(std::is_invocable_v<decltype(Data), const Type &>) {
if(auto *fallback = instance->try_cast<const Type>(); fallback) {
return meta_dispatch<Policy>(instance->context(), std::invoke(Data, *fallback));
}
}
}
return meta_any{meta_ctx_arg, instance->context()};
} else if constexpr(std::is_pointer_v<decltype(Data)>) {
if constexpr(std::is_array_v<std::remove_pointer_t<decltype(Data)>>) {
return meta_any{meta_ctx_arg, instance->context()};
} else {
return meta_dispatch<Policy>(instance->context(), *Data);
}
} else {
return meta_dispatch<Policy>(instance->context(), Data);
}
}
/**
* @brief Gets the value of a given variable.
* @tparam Type Reflected type to which the variable is associated.
* @tparam Data The actual variable to get.
* @tparam Policy Optional policy (no policy set by default).
* @param ctx The context from which to search for meta types.
* @param instance An opaque instance of the underlying type, if required.
* @return A meta any containing the value of the underlying variable.
*/
template<typename Type, auto Data, typename Policy = as_is_t>
[[deprecated("a context is no longer required, it is inferred from the meta_handle")]] [[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_getter(const meta_ctx &ctx, meta_handle instance) {
return meta_getter<Type, Data, Policy>(meta_handle{ctx, std::move(instance)});
}
/**
* @brief Tries to _invoke_ an object given a list of erased parameters.
* @tparam Type Reflected type to which the object to _invoke_ is associated.
* @tparam Policy Optional policy (no policy set by default).
* @tparam Candidate The type of the actual object to _invoke_.
* @param instance An opaque instance of the underlying type, if required.
* @param candidate The actual object to _invoke_.
* @param args Parameters to use to _invoke_ the object.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, typename Policy = as_is_t, typename Candidate>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_invoke(meta_handle instance, Candidate &&candidate, meta_any *const args) {
return internal::meta_invoke<Type, Policy>(std::move(instance), std::forward<Candidate>(candidate), args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::args_type::size>{});
}
/**
* @brief Tries to _invoke_ an object given a list of erased parameters.
* @tparam Type Reflected type to which the object to _invoke_ is associated.
* @tparam Policy Optional policy (no policy set by default).
* @param ctx The context from which to search for meta types.
* @tparam Candidate The type of the actual object to _invoke_.
* @param instance An opaque instance of the underlying type, if required.
* @param candidate The actual object to _invoke_.
* @param args Parameters to use to _invoke_ the object.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, typename Policy = as_is_t, typename Candidate>
[[deprecated("a context is no longer required, it is inferred from the meta_handle")]] [[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_invoke(const meta_ctx &ctx, meta_handle instance, Candidate &&candidate, meta_any *const args) {
return meta_invoke<Type, Policy>(meta_handle{ctx, std::move(instance)}, std::forward<Candidate>(candidate), args);
}
/**
* @brief Tries to invoke a function given a list of erased parameters.
* @tparam Type Reflected type to which the function is associated.
* @tparam Candidate The actual function to invoke.
* @tparam Policy Optional policy (no policy set by default).
* @param instance An opaque instance of the underlying type, if required.
* @param args Parameters to use to invoke the function.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, auto Candidate, typename Policy = as_is_t>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_invoke(meta_handle instance, meta_any *const args) {
return internal::meta_invoke<Type, Policy>(std::move(instance), Candidate, args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<decltype(Candidate)>>::args_type::size>{});
}
/**
* @brief Tries to invoke a function given a list of erased parameters.
* @tparam Type Reflected type to which the function is associated.
* @tparam Candidate The actual function to invoke.
* @tparam Policy Optional policy (no policy set by default).
* @param ctx The context from which to search for meta types.
* @param instance An opaque instance of the underlying type, if required.
* @param args Parameters to use to invoke the function.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, auto Candidate, typename Policy = as_is_t>
[[deprecated("a context is no longer required, it is inferred from the meta_handle")]] [[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_invoke(const meta_ctx &ctx, meta_handle instance, meta_any *const args) {
return meta_invoke<Type, Candidate, Policy>(meta_handle{ctx, std::move(instance)}, args);
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
*
* @warning
* The context provided is used only for the return type.<br/>
* It's up to the caller to bind the arguments to the right context(s).
*
* @tparam Type Actual type of the instance to construct.
* @tparam Args Types of arguments expected.
* @param ctx The context from which to search for meta types.
* @param args Parameters to use to construct the instance.
* @return A meta any containing the new instance, if any.
*/
template<typename Type, typename... Args>
[[nodiscard]] meta_any meta_construct(const meta_ctx &ctx, meta_any *const args) {
return internal::meta_construct<Type, Args...>(ctx, args, std::index_sequence_for<Args...>{});
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
* @tparam Type Actual type of the instance to construct.
* @tparam Args Types of arguments expected.
* @param args Parameters to use to construct the instance.
* @return A meta any containing the new instance, if any.
*/
template<typename Type, typename... Args>
[[nodiscard]] meta_any meta_construct(meta_any *const args) {
return meta_construct<Type, Args...>(locator<meta_ctx>::value_or(), args);
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
*
* @warning
* The context provided is used only for the return type.<br/>
* It's up to the caller to bind the arguments to the right context(s).
*
* @tparam Type Reflected type to which the object to _invoke_ is associated.
* @tparam Policy Optional policy (no policy set by default).
* @tparam Candidate The type of the actual object to _invoke_.
* @param ctx The context from which to search for meta types.
* @param candidate The actual object to _invoke_.
* @param args Parameters to use to _invoke_ the object.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, typename Policy = as_is_t, typename Candidate>
[[nodiscard]] meta_any meta_construct(const meta_ctx &ctx, Candidate &&candidate, meta_any *const args) {
if constexpr(meta_function_helper_t<Type, Candidate>::is_static || std::is_class_v<std::remove_cv_t<std::remove_reference_t<Candidate>>>) {
return internal::meta_invoke<Type, Policy>(meta_handle{meta_ctx_arg, ctx}, std::forward<Candidate>(candidate), args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::args_type::size>{});
} else {
meta_any handle{ctx, args->as_ref()};
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - waiting for C++20 (and std::span)
return internal::meta_invoke<Type, Policy>(handle, std::forward<Candidate>(candidate), args + 1u, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::args_type::size>{});
}
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
* @tparam Type Reflected type to which the object to _invoke_ is associated.
* @tparam Policy Optional policy (no policy set by default).
* @tparam Candidate The type of the actual object to _invoke_.
* @param candidate The actual object to _invoke_.
* @param args Parameters to use to _invoke_ the object.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, typename Policy = as_is_t, typename Candidate>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_construct(Candidate &&candidate, meta_any *const args) {
return meta_construct<Type, Policy>(locator<meta_ctx>::value_or(), std::forward<Candidate>(candidate), args);
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
*
* @warning
* The context provided is used only for the return type.<br/>
* It's up to the caller to bind the arguments to the right context(s).
*
* @tparam Type Reflected type to which the function is associated.
* @tparam Candidate The actual function to invoke.
* @tparam Policy Optional policy (no policy set by default).
* @param ctx The context from which to search for meta types.
* @param args Parameters to use to invoke the function.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, auto Candidate, typename Policy = as_is_t>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_construct(const meta_ctx &ctx, meta_any *const args) {
return meta_construct<Type, Policy>(ctx, Candidate, args);
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
* @tparam Type Reflected type to which the function is associated.
* @tparam Candidate The actual function to invoke.
* @tparam Policy Optional policy (no policy set by default).
* @param args Parameters to use to invoke the function.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, auto Candidate, typename Policy = as_is_t>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_construct(meta_any *const args) {
return meta_construct<Type, Candidate, Policy>(locator<meta_ctx>::value_or(), args);
}
} // namespace entt
#endif

21
src/external/entt/src/entt/poly/fwd.hpp vendored Normal file
View File

@@ -0,0 +1,21 @@
#ifndef ENTT_POLY_FWD_HPP
#define ENTT_POLY_FWD_HPP
#include <cstddef>
namespace entt {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
template<typename, std::size_t Len = sizeof(double[2]), std::size_t = alignof(double[2])>
class basic_poly;
/**
* @brief Alias declaration for the most common use case.
* @tparam Concept Concept descriptor.
*/
template<typename Concept>
using poly = basic_poly<Concept>;
} // namespace entt
#endif

311
src/external/entt/src/entt/poly/poly.hpp vendored Normal file
View File

@@ -0,0 +1,311 @@
#ifndef ENTT_POLY_POLY_HPP
#define ENTT_POLY_POLY_HPP
#include <cstddef>
#include <functional>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../core/any.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "fwd.hpp"
namespace entt {
/*! @brief Inspector class used to infer the type of the virtual table. */
struct poly_inspector {
/**
* @brief Generic conversion operator (definition only).
* @tparam Type Type to which conversion is requested.
*/
template<typename Type>
operator Type &&() const;
/**
* @brief Dummy invocation function (definition only).
* @tparam Member Index of the function to invoke.
* @tparam Args Types of arguments to pass to the function.
* @param args The arguments to pass to the function.
* @return A poly inspector convertible to any type.
*/
template<std::size_t Member, typename... Args>
[[nodiscard]] poly_inspector invoke(Args &&...args) const;
/*! @copydoc invoke */
template<std::size_t Member, typename... Args>
[[nodiscard]] poly_inspector invoke(Args &&...args);
};
/**
* @brief Static virtual table factory.
* @tparam Concept Concept descriptor.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Alignment requirement.
*/
template<typename Concept, std::size_t Len, std::size_t Align>
class poly_vtable {
using inspector = typename Concept::template type<poly_inspector>;
template<typename Ret, typename Clazz, typename... Args>
static auto vtable_entry(Ret (*)(Clazz &, Args...))
-> std::enable_if_t<std::is_base_of_v<std::remove_const_t<Clazz>, inspector>, Ret (*)(constness_as_t<basic_any<Len, Align>, Clazz> &, Args...)>;
template<typename Ret, typename... Args>
static auto vtable_entry(Ret (*)(Args...))
-> Ret (*)(const basic_any<Len, Align> &, Args...);
template<typename Ret, typename Clazz, typename... Args>
static auto vtable_entry(Ret (Clazz::*)(Args...))
-> std::enable_if_t<std::is_base_of_v<Clazz, inspector>, Ret (*)(basic_any<Len, Align> &, Args...)>;
template<typename Ret, typename Clazz, typename... Args>
static auto vtable_entry(Ret (Clazz::*)(Args...) const)
-> std::enable_if_t<std::is_base_of_v<Clazz, inspector>, Ret (*)(const basic_any<Len, Align> &, Args...)>;
template<auto... Candidate>
static auto make_vtable(value_list<Candidate...>) noexcept
-> decltype(std::make_tuple(vtable_entry(Candidate)...));
template<typename... Func>
[[nodiscard]] static constexpr auto make_vtable(type_list<Func...>) noexcept {
if constexpr(sizeof...(Func) == 0u) {
return decltype(make_vtable(typename Concept::template impl<inspector>{})){};
} else if constexpr((std::is_function_v<Func> && ...)) {
return decltype(std::make_tuple(vtable_entry(std::declval<Func inspector::*>())...)){};
}
}
template<typename Type, auto Candidate, typename Ret, typename Any, typename... Args>
static void fill_vtable_entry(Ret (*&entry)(Any &, Args...)) noexcept {
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Args...>) {
entry = +[](Any &, Args... args) -> Ret {
return std::invoke(Candidate, std::forward<Args>(args)...);
};
} else {
entry = +[](Any &instance, Args... args) -> Ret {
return static_cast<Ret>(std::invoke(Candidate, any_cast<constness_as_t<Type, Any> &>(instance), std::forward<Args>(args)...));
};
}
}
template<typename Type, auto... Index>
[[nodiscard]] static auto fill_vtable(std::index_sequence<Index...>) noexcept {
vtable_type impl{};
(fill_vtable_entry<Type, value_list_element_v<Index, typename Concept::template impl<Type>>>(std::get<Index>(impl)), ...);
return impl;
}
using vtable_type = decltype(make_vtable(Concept{}));
static constexpr bool is_mono = std::tuple_size_v<vtable_type> == 1u;
public:
/*! @brief Virtual table type. */
using type = std::conditional_t<is_mono, std::tuple_element_t<0u, vtable_type>, const vtable_type *>;
/**
* @brief Returns a static virtual table for a specific concept and type.
* @tparam Type The type for which to generate the virtual table.
* @return A static virtual table for the given concept and type.
*/
template<typename Type>
[[nodiscard]] static type instance() noexcept {
static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Type differs from its decayed form");
static const vtable_type vtable = fill_vtable<Type>(std::make_index_sequence<Concept::template impl<Type>::size>{});
if constexpr(is_mono) {
return std::get<0>(vtable);
} else {
return &vtable;
}
}
};
/**
* @brief Poly base class used to inject functionalities into concepts.
* @tparam Poly The outermost poly class.
*/
template<typename Poly>
struct poly_base {
/**
* @brief Invokes a function from the static virtual table.
* @tparam Member Index of the function to invoke.
* @tparam Args Types of arguments to pass to the function.
* @param self A reference to the poly object that made the call.
* @param args The arguments to pass to the function.
* @return The return value of the invoked function, if any.
*/
template<std::size_t Member, typename... Args>
[[nodiscard]] decltype(auto) invoke(const poly_base &self, Args &&...args) const {
const auto &poly = static_cast<const Poly &>(self);
if constexpr(std::is_function_v<std::remove_pointer_t<decltype(poly.vtable)>>) {
return poly.vtable(poly.storage, std::forward<Args>(args)...);
} else {
return std::get<Member>(*poly.vtable)(poly.storage, std::forward<Args>(args)...);
}
}
/*! @copydoc invoke */
template<std::size_t Member, typename... Args>
[[nodiscard]] decltype(auto) invoke(poly_base &self, Args &&...args) {
auto &poly = static_cast<Poly &>(self);
if constexpr(std::is_function_v<std::remove_pointer_t<decltype(poly.vtable)>>) {
static_assert(Member == 0u, "Unknown member");
return poly.vtable(poly.storage, std::forward<Args>(args)...);
} else {
return std::get<Member>(*poly.vtable)(poly.storage, std::forward<Args>(args)...);
}
}
};
/**
* @brief Shortcut for calling `poly_base<Type>::invoke`.
* @tparam Member Index of the function to invoke.
* @tparam Poly A fully defined poly object.
* @tparam Args Types of arguments to pass to the function.
* @param self A reference to the poly object that made the call.
* @param args The arguments to pass to the function.
* @return The return value of the invoked function, if any.
*/
template<std::size_t Member, typename Poly, typename... Args>
decltype(auto) poly_call(Poly &&self, Args &&...args) {
return std::forward<Poly>(self).template invoke<Member>(self, std::forward<Args>(args)...);
}
/**
* @brief Static polymorphism made simple and within everyone's reach.
*
* Static polymorphism is a very powerful tool in C++, albeit sometimes
* cumbersome to obtain.<br/>
* This class aims to make it simple and easy to use.
*
* @note
* Both deduced and defined static virtual tables are supported.<br/>
* Moreover, the `poly` class template also works with unmanaged objects.
*
* @tparam Concept Concept descriptor.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
*/
template<typename Concept, std::size_t Len, std::size_t Align>
class basic_poly: private Concept::template type<poly_base<basic_poly<Concept, Len, Align>>> {
friend struct poly_base<basic_poly>;
public:
/*! @brief Concept type. */
using concept_type = typename Concept::template type<poly_base<basic_poly>>;
/*! @brief Virtual table type. */
using vtable_type = typename poly_vtable<Concept, Len, Align>::type;
/*! @brief Default constructor. */
basic_poly() noexcept = default;
/**
* @brief Constructs a poly by directly initializing the new object.
* @tparam Type Type of object to use to initialize the poly.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<typename Type, typename... Args>
explicit basic_poly(std::in_place_type_t<Type>, Args &&...args)
: storage{std::in_place_type<Type>, std::forward<Args>(args)...},
vtable{poly_vtable<Concept, Len, Align>::template instance<std::remove_cv_t<std::remove_reference_t<Type>>>()} {}
/**
* @brief Constructs a poly from a given value.
* @tparam Type Type of object to use to initialize the poly.
* @param value An instance of an object to use to initialize the poly.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, basic_poly>>>
basic_poly(Type &&value) noexcept
: basic_poly{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)} {}
/**
* @brief Returns the object type if any, `type_id<void>()` otherwise.
* @return The object type if any, `type_id<void>()` otherwise.
*/
[[nodiscard]] const type_info &type() const noexcept {
return storage.type();
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] const void *data() const noexcept {
return storage.data();
}
/*! @copydoc data */
[[nodiscard]] void *data() noexcept {
return storage.data();
}
/**
* @brief Replaces the contained object by creating a new instance directly.
* @tparam Type Type of object to use to initialize the poly.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<typename Type, typename... Args>
void emplace(Args &&...args) {
storage.template emplace<Type>(std::forward<Args>(args)...);
vtable = poly_vtable<Concept, Len, Align>::template instance<std::remove_cv_t<std::remove_reference_t<Type>>>();
}
/*! @brief Destroys contained object */
void reset() {
storage.reset();
vtable = {};
}
/**
* @brief Returns false if a poly is empty, true otherwise.
* @return False if the poly is empty, true otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return static_cast<bool>(storage);
}
/**
* @brief Returns a pointer to the underlying concept.
* @return A pointer to the underlying concept.
*/
[[nodiscard]] concept_type *operator->() noexcept {
return this;
}
/*! @copydoc operator-> */
[[nodiscard]] const concept_type *operator->() const noexcept {
return this;
}
/**
* @brief Aliasing constructor.
* @return A poly that shares a reference to an unmanaged object.
*/
[[nodiscard]] basic_poly as_ref() noexcept {
basic_poly ref{};
ref.storage = storage.as_ref();
ref.vtable = vtable;
return ref;
}
/*! @copydoc as_ref */
[[nodiscard]] basic_poly as_ref() const noexcept {
basic_poly ref{};
ref.storage = storage.as_ref();
ref.vtable = vtable;
return ref;
}
private:
basic_any<Len, Align> storage{};
vtable_type vtable{};
};
} // namespace entt
#endif

View File

@@ -0,0 +1,20 @@
#ifndef ENTT_PROCESS_FWD_HPP
#define ENTT_PROCESS_FWD_HPP
#include <cstdint>
#include <memory>
namespace entt {
template<typename, typename>
class process;
template<typename = std::uint32_t, typename = std::allocator<void>>
class basic_scheduler;
/*! @brief Alias declaration for the most common use case. */
using scheduler = basic_scheduler<>;
} // namespace entt
#endif

View File

@@ -0,0 +1,354 @@
#ifndef ENTT_PROCESS_PROCESS_HPP
#define ENTT_PROCESS_PROCESS_HPP
#include <cstdint>
#include <type_traits>
#include <utility>
#include "fwd.hpp"
namespace entt {
/**
* @brief Base class for processes.
*
* This class stays true to the CRTP idiom. Derived classes must specify what's
* the intended type for elapsed times.<br/>
* A process should expose publicly the following member functions whether
* required:
*
* * @code{.cpp}
* void update(Delta, void *);
* @endcode
*
* It's invoked once per tick until a process is explicitly aborted or it
* terminates either with or without errors. Even though it's not mandatory to
* declare this member function, as a rule of thumb each process should at
* least define it to work properly. The `void *` parameter is an opaque
* pointer to user data (if any) forwarded directly to the process during an
* update.
*
* * @code{.cpp}
* void init();
* @endcode
*
* It's invoked when the process joins the running queue of a scheduler. This
* happens as soon as it's attached to the scheduler if the process is a top
* level one, otherwise when it replaces its parent if the process is a
* continuation.
*
* * @code{.cpp}
* void succeeded();
* @endcode
*
* It's invoked in case of success, immediately after an update and during the
* same tick.
*
* * @code{.cpp}
* void failed();
* @endcode
*
* It's invoked in case of errors, immediately after an update and during the
* same tick.
*
* * @code{.cpp}
* void aborted();
* @endcode
*
* It's invoked only if a process is explicitly aborted. There is no guarantee
* that it executes in the same tick, this depends solely on whether the
* process is aborted immediately or not.
*
* Derived classes can change the internal state of a process by invoking the
* `succeed` and `fail` protected member functions and even pause or unpause the
* process itself.
*
* @sa scheduler
*
* @tparam Derived Actual type of process that extends the class template.
* @tparam Delta Type to use to provide elapsed time.
*/
template<typename Derived, typename Delta>
class process {
enum class state : std::uint8_t {
uninitialized = 0,
running,
paused,
succeeded,
failed,
aborted,
finished,
rejected
};
template<typename Target = Derived>
auto next(std::integral_constant<state, state::uninitialized>)
-> decltype(std::declval<Target>().init(), void()) {
static_cast<Target *>(this)->init();
}
template<typename Target = Derived>
auto next(std::integral_constant<state, state::running>, Delta delta, void *data)
-> decltype(std::declval<Target>().update(delta, data), void()) {
static_cast<Target *>(this)->update(delta, data);
}
template<typename Target = Derived>
auto next(std::integral_constant<state, state::succeeded>)
-> decltype(std::declval<Target>().succeeded(), void()) {
static_cast<Target *>(this)->succeeded();
}
template<typename Target = Derived>
auto next(std::integral_constant<state, state::failed>)
-> decltype(std::declval<Target>().failed(), void()) {
static_cast<Target *>(this)->failed();
}
template<typename Target = Derived>
auto next(std::integral_constant<state, state::aborted>)
-> decltype(std::declval<Target>().aborted(), void()) {
static_cast<Target *>(this)->aborted();
}
template<typename... Args>
void next(Args...) const noexcept {}
protected:
/**
* @brief Terminates a process with success if it's still alive.
*
* The function is idempotent and it does nothing if the process isn't
* alive.
*/
void succeed() noexcept {
if(alive()) {
current = state::succeeded;
}
}
/**
* @brief Terminates a process with errors if it's still alive.
*
* The function is idempotent and it does nothing if the process isn't
* alive.
*/
void fail() noexcept {
if(alive()) {
current = state::failed;
}
}
/**
* @brief Stops a process if it's in a running state.
*
* The function is idempotent and it does nothing if the process isn't
* running.
*/
void pause() noexcept {
if(current == state::running) {
current = state::paused;
}
}
/**
* @brief Restarts a process if it's paused.
*
* The function is idempotent and it does nothing if the process isn't
* paused.
*/
void unpause() noexcept {
if(current == state::paused) {
current = state::running;
}
}
public:
/*! @brief Type used to provide elapsed time. */
using delta_type = Delta;
/*! @brief Default constructor. */
constexpr process() = default;
/*! @brief Default copy constructor. */
process(const process &) = default;
/*! @brief Default move constructor. */
process(process &&) noexcept = default;
/**
* @brief Default copy assignment operator.
* @return This process.
*/
process &operator=(const process &) = default;
/**
* @brief Default move assignment operator.
* @return This process.
*/
process &operator=(process &&) noexcept = default;
/*! @brief Default destructor. */
virtual ~process() {
static_assert(std::is_base_of_v<process, Derived>, "Incorrect use of the class template");
}
/**
* @brief Aborts a process if it's still alive.
*
* The function is idempotent and it does nothing if the process isn't
* alive.
*
* @param immediate Requests an immediate operation.
*/
void abort(const bool immediate = false) {
if(alive()) {
current = state::aborted;
if(immediate) {
tick({});
}
}
}
/**
* @brief Returns true if a process is either running or paused.
* @return True if the process is still alive, false otherwise.
*/
[[nodiscard]] bool alive() const noexcept {
return current == state::running || current == state::paused;
}
/**
* @brief Returns true if a process is already terminated.
* @return True if the process is terminated, false otherwise.
*/
[[nodiscard]] bool finished() const noexcept {
return current == state::finished;
}
/**
* @brief Returns true if a process is currently paused.
* @return True if the process is paused, false otherwise.
*/
[[nodiscard]] bool paused() const noexcept {
return current == state::paused;
}
/**
* @brief Returns true if a process terminated with errors.
* @return True if the process terminated with errors, false otherwise.
*/
[[nodiscard]] bool rejected() const noexcept {
return current == state::rejected;
}
/**
* @brief Updates a process and its internal state if required.
* @param delta Elapsed time.
* @param data Optional data.
*/
void tick(const Delta delta, void *data = nullptr) {
switch(current) {
case state::uninitialized:
next(std::integral_constant<state, state::uninitialized>{});
current = state::running;
break;
case state::running:
next(std::integral_constant<state, state::running>{}, delta, data);
break;
default:
// suppress warnings
break;
}
// if it's dead, it must be notified and removed immediately
switch(current) {
case state::succeeded:
next(std::integral_constant<state, state::succeeded>{});
current = state::finished;
break;
case state::failed:
next(std::integral_constant<state, state::failed>{});
current = state::rejected;
break;
case state::aborted:
next(std::integral_constant<state, state::aborted>{});
current = state::rejected;
break;
default:
// suppress warnings
break;
}
}
private:
state current{state::uninitialized};
};
/**
* @brief Adaptor for lambdas and functors to turn them into processes.
*
* Lambdas and functors can't be used directly with a scheduler for they are not
* properly defined processes with managed life cycles.<br/>
* This class helps in filling the gap and turning lambdas and functors into
* full featured processes usable by a scheduler.
*
* The signature of the function call operator should be equivalent to the
* following:
*
* @code{.cpp}
* void(Delta delta, void *data, auto succeed, auto fail);
* @endcode
*
* Where:
*
* * `delta` is the elapsed time.
* * `data` is an opaque pointer to user data if any, `nullptr` otherwise.
* * `succeed` is a function to call when a process terminates with success.
* * `fail` is a function to call when a process terminates with errors.
*
* The signature of the function call operator of both `succeed` and `fail`
* is equivalent to the following:
*
* @code{.cpp}
* void();
* @endcode
*
* Usually users shouldn't worry about creating adaptors. A scheduler will
* create them internally each and avery time a lambda or a functor is used as
* a process.
*
* @sa process
* @sa scheduler
*
* @tparam Func Actual type of process.
* @tparam Delta Type to use to provide elapsed time.
*/
template<typename Func, typename Delta>
struct process_adaptor: process<process_adaptor<Func, Delta>, Delta>, private Func {
/**
* @brief Constructs a process adaptor from a lambda or a functor.
* @tparam Args Types of arguments to use to initialize the actual process.
* @param args Parameters to use to initialize the actual process.
*/
template<typename... Args>
process_adaptor(Args &&...args)
: Func{std::forward<Args>(args)...} {}
/**
* @brief Updates a process and its internal state if required.
* @param delta Elapsed time.
* @param data Optional data.
*/
void update(const Delta delta, void *data) {
Func::operator()(
delta,
data,
[this]() { this->succeed(); },
[this]() { this->fail(); });
}
};
} // namespace entt
#endif

View File

@@ -0,0 +1,364 @@
#ifndef ENTT_PROCESS_SCHEDULER_HPP
#define ENTT_PROCESS_SCHEDULER_HPP
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../core/compressed_pair.hpp"
#include "fwd.hpp"
#include "process.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Delta>
struct basic_process_handler {
virtual ~basic_process_handler() = default;
virtual bool update(Delta, void *) = 0;
virtual void abort(bool) = 0;
// std::shared_ptr because of its type erased allocator which is useful here
std::shared_ptr<basic_process_handler> next;
};
template<typename Delta, typename Type>
struct process_handler final: basic_process_handler<Delta> {
template<typename... Args>
process_handler(Args &&...args)
: process{std::forward<Args>(args)...} {}
bool update(const Delta delta, void *data) override {
if(process.tick(delta, data); process.rejected()) {
this->next.reset();
}
return (process.rejected() || process.finished());
}
void abort(const bool immediate) override {
process.abort(immediate);
}
Type process;
};
} // namespace internal
/*! @endcond */
/**
* @brief Cooperative scheduler for processes.
*
* A cooperative scheduler runs processes and helps managing their life cycles.
*
* Each process is invoked once per tick. If a process terminates, it's
* removed automatically from the scheduler and it's never invoked again.<br/>
* A process can also have a child. In this case, the process is replaced with
* its child when it terminates if it returns with success. In case of errors,
* both the process and its child are discarded.
*
* Example of use (pseudocode):
*
* @code{.cpp}
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
* // code
* }).then<my_process>(arguments...);
* @endcode
*
* In order to invoke all scheduled processes, call the `update` member function
* passing it the elapsed time to forward to the tasks.
*
* @sa process
*
* @tparam Delta Type to use to provide elapsed time.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Delta, typename Allocator>
class basic_scheduler {
template<typename Type>
using handler_type = internal::process_handler<Delta, Type>;
// std::shared_ptr because of its type erased allocator which is useful here
using process_type = std::shared_ptr<internal::basic_process_handler<Delta>>;
using alloc_traits = std::allocator_traits<Allocator>;
using container_allocator = typename alloc_traits::template rebind_alloc<process_type>;
using container_type = std::vector<process_type, container_allocator>;
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Unsigned integer type. */
using delta_type = Delta;
/*! @brief Default constructor. */
basic_scheduler()
: basic_scheduler{allocator_type{}} {}
/**
* @brief Constructs a scheduler with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_scheduler(const allocator_type &allocator)
: handlers{allocator, allocator} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_scheduler(const basic_scheduler &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_scheduler(basic_scheduler &&other) noexcept
: handlers{std::move(other.handlers)} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_scheduler(basic_scheduler &&other, const allocator_type &allocator)
: handlers{container_type{std::move(other.handlers.first()), allocator}, allocator} {
ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a scheduler is not allowed");
}
/*! @brief Default destructor. */
~basic_scheduler() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This process scheduler.
*/
basic_scheduler &operator=(const basic_scheduler &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This process scheduler.
*/
basic_scheduler &operator=(basic_scheduler &&other) noexcept {
ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a scheduler is not allowed");
swap(other);
return *this;
}
/**
* @brief Exchanges the contents with those of a given scheduler.
* @param other Scheduler to exchange the content with.
*/
void swap(basic_scheduler &other) noexcept {
using std::swap;
swap(handlers, other.handlers);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return handlers.second();
}
/**
* @brief Number of processes currently scheduled.
* @return Number of processes currently scheduled.
*/
[[nodiscard]] size_type size() const noexcept {
return handlers.first().size();
}
/**
* @brief Returns true if at least a process is currently scheduled.
* @return True if there are scheduled processes, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
return handlers.first().empty();
}
/**
* @brief Discards all scheduled processes.
*
* Processes aren't aborted. They are discarded along with their children
* and never executed again.
*/
void clear() {
handlers.first().clear();
}
/**
* @brief Schedules a process for the next tick.
*
* Returned value can be used to attach a continuation for the last process.
* The continutation is scheduled automatically when the process terminates
* and only if the process returns with success.
*
* Example of use (pseudocode):
*
* @code{.cpp}
* // schedules a task in the form of a process class
* scheduler.attach<my_process>(arguments...)
* // appends a child in the form of a lambda function
* .then([](auto delta, void *, auto succeed, auto fail) {
* // code
* })
* // appends a child in the form of another process class
* .then<my_other_process>();
* @endcode
*
* @tparam Proc Type of process to schedule.
* @tparam Args Types of arguments to use to initialize the process.
* @param args Parameters to use to initialize the process.
* @return This process scheduler.
*/
template<typename Proc, typename... Args>
basic_scheduler &attach(Args &&...args) {
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
auto &ref = handlers.first().emplace_back(std::allocate_shared<handler_type<Proc>>(handlers.second(), std::forward<Args>(args)...));
// forces the process to exit the uninitialized state
ref->update({}, nullptr);
return *this;
}
/**
* @brief Schedules a process for the next tick.
*
* A process can be either a lambda or a functor. The scheduler wraps both
* of them in a process adaptor internally.<br/>
* The signature of the function call operator should be equivalent to the
* following:
*
* @code{.cpp}
* void(Delta delta, void *data, auto succeed, auto fail);
* @endcode
*
* Where:
*
* * `delta` is the elapsed time.
* * `data` is an opaque pointer to user data if any, `nullptr` otherwise.
* * `succeed` is a function to call when a process terminates with success.
* * `fail` is a function to call when a process terminates with errors.
*
* The signature of the function call operator of both `succeed` and `fail`
* is equivalent to the following:
*
* @code{.cpp}
* void();
* @endcode
*
* Returned value can be used to attach a continuation for the last process.
* The continutation is scheduled automatically when the process terminates
* and only if the process returns with success.
*
* Example of use (pseudocode):
*
* @code{.cpp}
* // schedules a task in the form of a lambda function
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
* // code
* })
* // appends a child in the form of another lambda function
* .then([](auto delta, void *, auto succeed, auto fail) {
* // code
* })
* // appends a child in the form of a process class
* .then<my_process>(arguments...);
* @endcode
*
* @sa process_adaptor
*
* @tparam Func Type of process to schedule.
* @param func Either a lambda or a functor to use as a process.
* @return This process scheduler.
*/
template<typename Func>
basic_scheduler &attach(Func &&func) {
using Proc = process_adaptor<std::decay_t<Func>, Delta>;
return attach<Proc>(std::forward<Func>(func));
}
/**
* @brief Sets a process as a continuation of the last scheduled process.
* @tparam Proc Type of process to use as a continuation.
* @tparam Args Types of arguments to use to initialize the process.
* @param args Parameters to use to initialize the process.
* @return This process scheduler.
*/
template<typename Proc, typename... Args>
basic_scheduler &then(Args &&...args) {
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
ENTT_ASSERT(!handlers.first().empty(), "Process not available");
auto *curr = handlers.first().back().get();
for(; curr->next; curr = curr->next.get()) {}
curr->next = std::allocate_shared<handler_type<Proc>>(handlers.second(), std::forward<Args>(args)...);
return *this;
}
/**
* @brief Sets a process as a continuation of the last scheduled process.
* @tparam Func Type of process to use as a continuation.
* @param func Either a lambda or a functor to use as a process.
* @return This process scheduler.
*/
template<typename Func>
basic_scheduler &then(Func &&func) {
using Proc = process_adaptor<std::decay_t<Func>, Delta>;
return then<Proc>(std::forward<Func>(func));
}
/**
* @brief Updates all scheduled processes.
*
* All scheduled processes are executed in no specific order.<br/>
* If a process terminates with success, it's replaced with its child, if
* any. Otherwise, if a process terminates with an error, it's removed along
* with its child.
*
* @param delta Elapsed time.
* @param data Optional data.
*/
void update(const delta_type delta, void *data = nullptr) {
for(auto next = handlers.first().size(); next; --next) {
if(const auto pos = next - 1u; handlers.first()[pos]->update(delta, data)) {
// updating might spawn/reallocate, cannot hold refs until here
if(auto &curr = handlers.first()[pos]; curr->next) {
curr = std::move(curr->next);
// forces the process to exit the uninitialized state
curr->update({}, nullptr);
} else {
curr = std::move(handlers.first().back());
handlers.first().pop_back();
}
}
}
}
/**
* @brief Aborts all scheduled processes.
*
* Unless an immediate operation is requested, the abort is scheduled for
* the next tick. Processes won't be executed anymore in any case.<br/>
* Once a process is fully aborted and thus finished, it's discarded along
* with its child, if any.
*
* @param immediate Requests an immediate operation.
*/
void abort(const bool immediate = false) {
for(auto &&curr: handlers.first()) {
curr->abort(immediate);
}
}
private:
compressed_pair<container_type, allocator_type> handlers;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,413 @@
#ifndef ENTT_RESOURCE_RESOURCE_CACHE_HPP
#define ENTT_RESOURCE_RESOURCE_CACHE_HPP
#include <cstddef>
#include <functional>
#include <iterator>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../container/dense_map.hpp"
#include "../core/compressed_pair.hpp"
#include "../core/fwd.hpp"
#include "../core/iterator.hpp"
#include "../core/utility.hpp"
#include "fwd.hpp"
#include "loader.hpp"
#include "resource.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Type, typename It>
class resource_cache_iterator final {
template<typename, typename>
friend class resource_cache_iterator;
public:
using value_type = std::pair<id_type, resource<Type>>;
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
using iterator_concept = std::random_access_iterator_tag;
constexpr resource_cache_iterator() noexcept = default;
constexpr resource_cache_iterator(const It iter) noexcept
: it{iter} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
constexpr resource_cache_iterator(const resource_cache_iterator<std::remove_const_t<Type>, Other> &other) noexcept
: it{other.it} {}
constexpr resource_cache_iterator &operator++() noexcept {
return ++it, *this;
}
constexpr resource_cache_iterator operator++(int) noexcept {
const resource_cache_iterator orig = *this;
return ++(*this), orig;
}
constexpr resource_cache_iterator &operator--() noexcept {
return --it, *this;
}
constexpr resource_cache_iterator operator--(int) noexcept {
const resource_cache_iterator orig = *this;
return operator--(), orig;
}
constexpr resource_cache_iterator &operator+=(const difference_type value) noexcept {
it += value;
return *this;
}
constexpr resource_cache_iterator operator+(const difference_type value) const noexcept {
resource_cache_iterator copy = *this;
return (copy += value);
}
constexpr resource_cache_iterator &operator-=(const difference_type value) noexcept {
return (*this += -value);
}
constexpr resource_cache_iterator operator-(const difference_type value) const noexcept {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
return {it[value].first, resource<Type>{it[value].second}};
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return operator[](0);
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return operator*();
}
template<typename... Lhs, typename... Rhs>
friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
template<typename... Lhs, typename... Rhs>
friend constexpr bool operator==(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
template<typename... Lhs, typename... Rhs>
friend constexpr bool operator<(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
private:
It it;
};
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return lhs.it - rhs.it;
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator==(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return lhs.it == rhs.it;
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return !(lhs == rhs);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return lhs.it < rhs.it;
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return rhs < lhs;
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return !(lhs > rhs);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return !(lhs < rhs);
}
} // namespace internal
/*! @endcond */
/**
* @brief Basic cache for resources of any type.
* @tparam Type Type of resources managed by a cache.
* @tparam Loader Type of loader used to create the resources.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Loader, typename Allocator>
class resource_cache {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const id_type, typename Loader::result_type>>;
using container_type = dense_map<id_type, typename Loader::result_type, identity, std::equal_to<>, container_allocator>;
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Resource type. */
using value_type = Type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Loader type. */
using loader_type = Loader;
/*! @brief Input iterator type. */
using iterator = internal::resource_cache_iterator<Type, typename container_type::iterator>;
/*! @brief Constant input iterator type. */
using const_iterator = internal::resource_cache_iterator<const Type, typename container_type::const_iterator>;
/*! @brief Default constructor. */
resource_cache()
: resource_cache{loader_type{}} {}
/**
* @brief Constructs an empty cache with a given allocator.
* @param allocator The allocator to use.
*/
explicit resource_cache(const allocator_type &allocator)
: resource_cache{loader_type{}, allocator} {}
/**
* @brief Constructs an empty cache with a given allocator and loader.
* @param callable The loader to use.
* @param allocator The allocator to use.
*/
explicit resource_cache(const loader_type &callable, const allocator_type &allocator = allocator_type{})
: pool{container_type{allocator}, callable} {}
/*! @brief Default copy constructor. */
resource_cache(const resource_cache &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
resource_cache(const resource_cache &other, const allocator_type &allocator)
: pool{std::piecewise_construct, std::forward_as_tuple(other.pool.first(), allocator), std::forward_as_tuple(other.pool.second())} {}
/*! @brief Default move constructor. */
resource_cache(resource_cache &&) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
resource_cache(resource_cache &&other, const allocator_type &allocator)
: pool{std::piecewise_construct, std::forward_as_tuple(std::move(other.pool.first()), allocator), std::forward_as_tuple(std::move(other.pool.second()))} {}
/*! @brief Default destructor. */
~resource_cache() = default;
/**
* @brief Default copy assignment operator.
* @return This cache.
*/
resource_cache &operator=(const resource_cache &) = default;
/**
* @brief Default move assignment operator.
* @return This cache.
*/
resource_cache &operator=(resource_cache &&) noexcept = default;
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return pool.first().get_allocator();
}
/**
* @brief Returns an iterator to the beginning.
*
* If the cache is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first instance of the internal cache.
*/
[[nodiscard]] const_iterator cbegin() const noexcept {
return pool.first().begin();
}
/*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept {
return cbegin();
}
/*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept {
return pool.first().begin();
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last instance of the
* internal cache.
*/
[[nodiscard]] const_iterator cend() const noexcept {
return pool.first().end();
}
/*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept {
return cend();
}
/*! @copydoc end */
[[nodiscard]] iterator end() noexcept {
return pool.first().end();
}
/**
* @brief Returns true if a cache contains no resources, false otherwise.
* @return True if the cache contains no resources, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
return pool.first().empty();
}
/**
* @brief Number of resources managed by a cache.
* @return Number of resources currently stored.
*/
[[nodiscard]] size_type size() const noexcept {
return pool.first().size();
}
/*! @brief Clears a cache. */
void clear() noexcept {
pool.first().clear();
}
/**
* @brief Loads a resource, if its identifier does not exist.
*
* Arguments are forwarded directly to the loader and _consumed_ only if the
* resource doesn't already exist.
*
* @warning
* If the resource isn't loaded correctly, the returned handle could be
* invalid and any use of it will result in undefined behavior.
*
* @tparam Args Types of arguments to use to load the resource if required.
* @param id Unique resource identifier.
* @param args Arguments to use to load the resource if required.
* @return A pair consisting of an iterator to the inserted element (or to
* the element that prevented the insertion) and a bool denoting whether the
* insertion took place.
*/
template<typename... Args>
std::pair<iterator, bool> load(const id_type id, Args &&...args) {
if(auto it = pool.first().find(id); it != pool.first().end()) {
return {it, false};
}
return pool.first().emplace(id, pool.second()(std::forward<Args>(args)...));
}
/**
* @brief Force loads a resource, if its identifier does not exist.
* @copydetails load
*/
template<typename... Args>
std::pair<iterator, bool> force_load(const id_type id, Args &&...args) {
return {pool.first().insert_or_assign(id, pool.second()(std::forward<Args>(args)...)).first, true};
}
/**
* @brief Returns a handle for a given resource identifier.
*
* @warning
* There is no guarantee that the returned handle is valid.<br/>
* If it is not, any use will result in indefinite behavior.
*
* @param id Unique resource identifier.
* @return A handle for the given resource.
*/
[[nodiscard]] resource<const value_type> operator[](const id_type id) const {
if(auto it = pool.first().find(id); it != pool.first().cend()) {
return resource<const value_type>{it->second};
}
return {};
}
/*! @copydoc operator[] */
[[nodiscard]] resource<value_type> operator[](const id_type id) {
if(auto it = pool.first().find(id); it != pool.first().end()) {
return resource<value_type>{it->second};
}
return {};
}
/**
* @brief Checks if a cache contains a given identifier.
* @param id Unique resource identifier.
* @return True if the cache contains the resource, false otherwise.
*/
[[nodiscard]] bool contains(const id_type id) const {
return pool.first().contains(id);
}
/**
* @brief Removes an element from a given position.
* @param pos An iterator to the element to remove.
* @return An iterator following the removed element.
*/
iterator erase(const_iterator pos) {
const auto it = pool.first().begin();
return pool.first().erase(it + (pos - const_iterator{it}));
}
/**
* @brief Removes the given elements from a cache.
* @param first An iterator to the first element of the range of elements.
* @param last An iterator past the last element of the range of elements.
* @return An iterator following the last removed element.
*/
iterator erase(const_iterator first, const_iterator last) {
const auto it = pool.first().begin();
return pool.first().erase(it + (first - const_iterator{it}), it + (last - const_iterator{it}));
}
/**
* @brief Removes the given elements from a cache.
* @param id Unique resource identifier.
* @return Number of resources erased (either 0 or 1).
*/
size_type erase(const id_type id) {
return pool.first().erase(id);
}
/**
* @brief Returns the loader used to create resources.
* @return The loader used to create resources.
*/
[[nodiscard]] loader_type loader() const {
return pool.second();
}
private:
compressed_pair<container_type, loader_type> pool;
};
} // namespace entt
#endif

Some files were not shown because too many files have changed in this diff Show More