Skip to content
Flecs v4.1
game_mechanics/factory

  • C++

    View on GitHub

    1#include <factory.h>
    2#include <iostream>
    3
    4// This example shows how a simple production hierarchy can be simulated in ECS.
    5// The example has the following concepts:
    6//
    7// - A recipe describes the requirements and time required to produce a resource
    8// - Factories produce resources based on an assigned recipe
    9// - Depots store resources
    10//
    11// A factory needs to be connected to inputs that provide the resource. Once the
    12// inputs are connected correctly, the factory will start producing the resource
    13// once it has collected all of the required resources from the inputs.
    14//
    15// Factories can act as inputs for other factories. Depots can be used to
    16// combine the output of multiple factories.
    17//
    18// The resource definitions and scene are loaded at runtime from resource.flecs
    19// and scene.flecs.
    20//
    21// Few notes:
    22// - The demo doesn't have graphics, but can be viewed with the explorer.
    23// - Components store references to entities as entity_t's. They could also
    24// flecs::entity's, but this requires a bit less memory.
    25// - Some data is duplicated between resource and factory components so that
    26// systems can mostly operate on local data
    27//
    28
    29using namespace flecs;
    30
    31// Factory module
    32struct factories {
    33 // Maximum number of resources a recipe can depend on
    34 static constexpr int MaxInputs = 3;
    35
    36
    37 // -- Recipe components
    38
    39 // Single resource requirement for a recipe
    40 struct Requirement {
    41 entity_t resource;
    42 int32_t amount;
    43 };
    44
    45 // All resource requirements for a recipe
    46 struct Requires {
    47 Requirement items[MaxInputs];
    48 };
    49
    50 // Time it takes to produce a resource
    51 struct TimeToProduce {
    52 float value;
    53 };
    54
    55 // Limit how many items can be stacked for a specific resource
    56 struct StackCount {
    57 int32_t amount;
    58 };
    59
    60
    61 // -- Depot & Factory components
    62
    63 // Resource storage
    64 struct Stores {
    65 int32_t amount;
    66 };
    67
    68 // Factory configuration
    69 struct Factory {
    70 // Recipe for the resource to produce
    71 entity_t recipe;
    72
    73 // Must provide resources as specified by recipe
    74 entity_t inputs[MaxInputs];
    75
    76 // Optional output which allows a factory to forward items to a depot
    77 entity_t output;
    78 };
    79
    80 // Factory state
    81 enum class FactoryState {
    82 // Factory isn't connected to (correct) inputs yet
    83 Idle,
    84
    85 // Factory is connected, but doesn't have the required resources yet
    86 WaitingForResources,
    87
    88 // Factory is producing resource
    89 Producing,
    90
    91 // Factory is done producing
    92 TransferResource
    93 };
    94
    95 // Tracks which resources have been supplied to factory
    96 struct FactorySupply {
    97 int32_t required[MaxInputs];
    98 int32_t collected[MaxInputs];
    99 flecs::ref<Stores> inputs[MaxInputs];
    100 flecs::ref<Stores> output;
    101 };
    102
    103 // Track production progress
    104 struct FactoryProduction {
    105 float value;
    106 float duration;
    107 int32_t max_stack;
    108 };
    109
    110 // Module import function
    111 factories(world& world) {
    112 // Units improve visualization of component values in the explorer.
    113 world.import<flecs::units>();
    114
    115 // -- Component registration
    116 // Reflection data is registered so we can view the components from the
    117 // explorer and assign them from .flecs files.
    118
    119 world.component<Stores>()
    120 .member<int32_t>("amount");
    121
    122 world.component<Requirement>()
    123 .member(flecs::Entity, "resource")
    124 .member<int32_t>("amount");
    125
    126 world.component<Requires>()
    127 .array<Requirement>(3);
    128
    129 world.component<FactorySupply>()
    130 .member<int32_t>("required", 3)
    131 .member<int32_t>("collected", 3);
    132
    133 world.component<TimeToProduce>()
    134 .member<float, units::duration::Seconds>("value");
    135
    136 world.component<StackCount>()
    137 .member<int32_t>("amount");
    138
    139 world.component<FactoryProduction>()
    140 .member<float, units::Percentage>("value")
    141 .member<float, units::duration::Seconds>("duration")
    142 .member<int32_t>("max_stack");
    143
    144 world.component<Factory>()
    145 // Factories start out idle
    146 .on_add([](entity factory, Factory) {
    147 factory.add(FactoryState::Idle);
    148 })
    149 // Check if factory is properly setup when value is assigned to component
    150 .on_set(factory_init)
    151 .member(flecs::Entity, "recipe")
    152 .member(flecs::Entity, "inputs", 3)
    153 .member(flecs::Entity, "output");
    154
    155 // System that collects resources from inputs
    156 world.system<FactorySupply>("Collect")
    157 .with(FactoryState::WaitingForResources)
    158 .interval(1.0f)
    159 .each([](entity factory, FactorySupply& s) {
    160 bool satisfied = true;
    161
    162 for (int i = 0; i < MaxInputs; i ++) {
    163 int32_t needed = s.required[i] - s.collected[i];
    164 if (needed) {
    165 Stores* p = s.inputs[i].get();
    166 if (p->amount >= needed) {
    167 s.collected[i] += needed;
    168 p->amount -= needed;
    169 } else {
    170 s.collected[i] += p->amount;
    171 p->amount = 0;
    172 satisfied = false;
    173 }
    174 }
    175 }
    176
    177 // If all resources are satisfied, change state of factory to producing
    178 if (satisfied) {
    179 // Consume collected resources
    180 for (int i = 0; i < MaxInputs; i ++) {
    181 s.collected[i] = 0;
    182 }
    183
    184 factory.add(FactoryState::Producing);
    185 }
    186 });
    187
    188 // System that produces a resource once all input requirements are met
    189 world.system<FactoryProduction>("Produce")
    190 .with(FactoryState::Producing)
    191 .interval(0.1f)
    192 .each([](flecs::iter& it, size_t i, FactoryProduction& p) {
    193 p.value += it.delta_system_time() / p.duration;
    194 if (p.value >= 1) {
    195 p.value = 1;
    196 it.entity(i).add(FactoryState::TransferResource);
    197 }
    198 });
    199
    200 // System that transfers resource and resets factory for next item
    201 world.system<FactorySupply, FactoryProduction, Stores>("Transfer")
    202 .term_at(2).second(Wildcard)
    203 .with(FactoryState::TransferResource)
    204 .interval(1.0f)
    205 .each([](entity factory, FactorySupply& s, FactoryProduction& p, Stores& out) {
    206 // Reset production progress
    207 p.value = 0;
    208
    209 // If depot is configured, transfer local resources first
    210 Stores *depot = s.output.try_get();
    211 if (depot) {
    212 int32_t available_space = p.max_stack - depot->amount;
    213 if (out.amount > available_space) {
    214 depot->amount += available_space;
    215 out.amount -= available_space;
    216 } else {
    217 depot->amount += out.amount;
    218 out.amount = 0;
    219 }
    220 }
    221
    222 // Now check which storage to output the new resource item to. If a
    223 // depot is configured, attempt to store it there, otherwise store it
    224 // in factory.
    225 Stores *store = &out;
    226 if (depot) {
    227 if (!p.max_stack || (depot->amount < p.max_stack)) {
    228 store = depot;
    229 }
    230 }
    231
    232 if (store->amount < p.max_stack) {
    233 // Add resource to storage and go back to collecting resources
    234 store->amount ++;
    235 factory.add(FactoryState::WaitingForResources);
    236 } else {
    237 // No space in output, do nothing
    238 }
    239 });
    240 }
    241
    242private:
    243 // on_set hook for Factory component. When Factory component value is written,
    244 // check if the factory has a recipe and whether the factory inputs satisfy
    245 // the recipe requirements.
    246 static void factory_init(entity factory, Factory& config) {
    247 world world = factory.world();
    248 entity recipe = world.entity(config.recipe);
    249
    250 recipe.get([&](const Requires& r) {
    251 entity output = world.entity(config.output);
    252 if (output) {
    253 if (!output.has<Stores>(recipe)) {
    254 std::cout << factory.path() << ": output doesn't provide resource "
    255 << recipe.path() << "\n";
    256 return;
    257 }
    258 }
    259
    260 // For each recipe requirement, make sure a correct input is connected
    261 bool satisfied = true;
    262 for (int i = 0; i < MaxInputs; i ++) {
    263 entity resource = world.entity(r.items[i].resource);
    264 entity input = world.entity(config.inputs[i]);
    265 if (!resource) {
    266 if (input) {
    267 std::cout << factory.path() <<
    268 ": input connected to empty input slot\n";
    269 }
    270 continue;
    271 }
    272
    273 if (resource && !input) {
    274 satisfied = false;
    275 break;
    276 }
    277
    278 if (!input.has<Stores>(resource)) {
    279 std::cout << factory.path() << ": input doesn't provide resource "
    280 << recipe.path() << "\n";
    281 satisfied = false;
    282 break;
    283 }
    284 }
    285
    286 // If we got through all requirements without issues, factory is ready
    287 // to start collecting resources
    288 if (satisfied) {
    289 factory.add(FactoryState::WaitingForResources);
    290
    291 // Initialize supply component
    292 factory.insert([&](FactorySupply& s) {
    293 for (int i = 0; i < MaxInputs; i ++) {
    294 entity resource = world.entity(r.items[i].resource);
    295 entity input = world.entity(config.inputs[i]);
    296 int32_t amount = r.items[i].amount;
    297
    298 s.required[i] = amount;
    299 s.collected[i] = 0;
    300
    301 if (!resource) {
    302 continue;
    303 }
    304
    305 // Take a ref to the resource provider for the factory input. Refs
    306 // provide quick and convenient access for repeatedly accessing a
    307 // component from the same entity.
    308 s.inputs[i] = input.get_ref<Stores>(resource);
    309 }
    310
    311 if (output) {
    312 // If the factory has an output configured, also take a ref to it so
    313 // we can quickly add the produced resources to it.
    314 s.output = output.get_ref<Stores>(recipe);
    315 }
    316 });
    317
    318 // Add component that tracks how much time is left to produce a resource
    319 recipe.get([&](const TimeToProduce& ttp, const StackCount& sc) {
    320 factory.set<FactoryProduction>({ 0, ttp.value, sc.amount });
    321 });
    322
    323 // Set output resource for factory
    324 factory.add<Stores>(recipe);
    325 }
    326 });
    327 }
    328};
    329
    330int main(int argc, char *argv[]) {
    331 flecs::world world(argc, argv);
    332
    333 world.import<factories>();
    334
    335 ecs_script_run_file(world, "resources.flecs");
    336 ecs_script_run_file(world, "scene.flecs");
    337
    338 return world.app()
    339 .enable_rest()
    340 .target_fps(60)
    341 .delta_time(1.0f / 60.0f) // Run at fixed time step
    342 .run();
    343}
    FLECS_API int ecs_script_run_file(ecs_world_t *world, const char *filename)
    Parse script file.
    flecs::app_builder app()
    Return an app builder.
    Definition world.hpp:20
    flecs::entity import()
    Import a module.
    Definition impl.hpp:150
    flecs::system system(flecs::entity e) const
    Upcast an entity to a system.
    Definition impl.hpp:181
    void interval(ecs_ftime_t interval)
    Set interval.
    Definition impl.hpp:72
    flecs::component< T > component(Args &&... args) const
    Find or register a component.
    Definition impl.hpp:11
    flecs::entity entity(Args &&... args) const
    Create an entity.
    Definition impl.hpp:195
    ecs_entity_t entity_t
    Entity type.
    Definition c_types.hpp:21
    int run()
    Run the application.
    Definition builder.hpp:82
    app_builder & enable_rest(uint16_t port=0)
    Enable the REST API.
    Definition builder.hpp:57
    app_builder & delta_time(ecs_ftime_t value)
    Set the fixed delta time for each frame.
    Definition builder.hpp:39
    app_builder & target_fps(ecs_ftime_t value)
    Set the target frames per second.
    Definition builder.hpp:33
    const Self & insert(const Func &func) const
    Set 1..N components.
    Definition impl.hpp:23
    flecs::string path(const char *sep="::", const char *init_sep="::") const
    Return the entity path.
    void each(const Func &func) const
    Iterate (component) IDs of an entity.
    Definition impl.hpp:155
    flecs::world world() const
    Get the world.
    Definition impl.hpp:60
    flecs::entity second() const
    Get second element from a pair.
    Definition impl.hpp:31
    ecs_ftime_t delta_system_time() const
    Get the time elapsed since the last system invocation.
    Definition iter.hpp:140
    flecs::entity entity(size_t row) const
    Obtain a mutable handle to the entity being iterated over.
    Definition iter.hpp:36
    The world.
    Definition world.hpp:129
    flecs::entity entity(Args &&... args) const
    Create an entity.