diff --git a/psemek b/psemek index 6368ca5..82f7d5d 160000 --- a/psemek +++ b/psemek @@ -1 +1 @@ -Subproject commit 6368ca5e680bbcae3d13df4e87ed25ef80728cd3 +Subproject commit 82f7d5d429f7f04a82235dd0b7023b23feb39eae diff --git a/source/application.cpp b/source/application.cpp index 67d8dca..9277d5d 100644 --- a/source/application.cpp +++ b/source/application.cpp @@ -10,9 +10,13 @@ #include #include #include +#include +#include #include +#include + #include #include #include @@ -39,123 +43,97 @@ namespace gmtk throw util::unknown_enum_value_exception{c}; } - struct tile; - - struct grid + struct location { - util::array tiles; - util::array item_target; + int level; + geom::point coords; - util::array>, 2> belts; + geom::point center() const + { + float s = std::pow(3.f, -level); + return {(coords[0] + 0.5f) * s, (coords[1] + 0.5f) * s}; + } - static grid create(); + geom::box bbox() const + { + float s = std::pow(3.f, -level); + return {{{(coords[0] + 0) * s, (coords[0] + 1) * s}, {(coords[1] + 0) * s, (coords[1] + 1) * s}}}; + } - static geom::point const indices[9]; + location down() const + { + return {level + 1, {coords[0] * 3 + 1, coords[1] * 3 + 1}}; + } - static geom::vector const neighbours[4]; + location up() const + { + return {level - 1, {geom::idiv(coords[0], 3), geom::idiv(coords[1], 3)}}; + } + + friend bool operator == (location const & x, location const & y) = default; + friend auto operator <=> (location const & x, location const & y) = default; }; - geom::point const grid::indices[9] = + struct location_hash { - {0, 0}, - {1, 0}, - {2, 0}, - {0, 1}, - {1, 1}, - {2, 1}, - {0, 2}, - {1, 2}, - {2, 2}, + std::size_t operator()(location const & x) const noexcept + { + return util::hash_all(x.level, x.coords[0], x.coords[1]); + } }; - geom::vector const grid::neighbours[4] = + struct vertex { - {1, 0}, - {0, 1}, - {-1, 0}, - {0, -1}, - }; + psemek_ecs_declare_uuid("vertex") - struct empty{}; + struct location location; + }; struct source { + psemek_ecs_declare_uuid("source") + color type; }; - struct factory + struct transformer { + psemek_ecs_declare_uuid("transformer") + color input; color output; }; - struct zoomer + struct path_vertex { - struct grid grid; + psemek_ecs_declare_uuid("path_vertex") + + struct location location; + + boost::container::flat_set belts = {}; }; - using tile_variant = std::variant; - - struct tile - : tile_variant + struct occupied { - using tile_variant::variant; + psemek_ecs_declare_uuid("occupied") + + ecs::handle entity; }; - grid grid::create() - { - grid result; - - result.tiles.resize({3, 3}); - result.belts.resize({3, 3}); - result.item_target.resize({9, 9}, false); - - return result; - } - struct item { + psemek_ecs_declare_uuid("item") + color type; - geom::point start; - geom::point target; - float pos; + location start; + ecs::handle target = ecs::handle::null(); + float state = 0.f; }; - geom::point item_to_cell(geom::point const & p) + bool within_grid(location const & l) { - return {geom::idiv(p[0], 3), geom::idiv(p[1], 3)}; - } - - geom::point cell_center_to_item(geom::point const & p) - { - return {p[0] * 3 + 1, p[1] * 3 + 1}; - } - - geom::point task_sink_to_item(geom::point p) - { - p[0] *= 3; - p[1] *= 3; - - if (p[0] == -3) - p += geom::vector{2, 1}; - else if (p[1] == -3) - p += geom::vector{1, 2}; - else if (p[0] == 9) - p += geom::vector{0, 1}; - else if (p[1] == 9) - p += geom::vector{1, 0}; - - return p; - } - - bool within_grid(geom::point const & p) - { - return p[0] >= 0 && p[0] < 3 && p[1] >= 0 && p[1] < 3; - } - - bool item_within_grid(geom::point const & p) - { - return p[0] >= 0 && p[0] < 9 && p[1] >= 0 && p[1] < 9; + int max = std::pow(3, l.level + 1); + return l.coords[0] >= 0 && l.coords[0] < max && l.coords[1] >= 0 && l.coords[1] < 3; } struct timestamp @@ -177,6 +155,8 @@ namespace gmtk struct task { + psemek_ecs_declare_uuid("task") + color type; // In last 15 seconds @@ -187,33 +167,82 @@ namespace gmtk struct map { - struct grid grid; + ecs::container world; - util::hash_map, task> tasks; - - std::vector items; + int current_level = 0; + geom::point current_origin = {0, 0}; timestamp time = {}; float spawn_timer = 0.f; }; + template + struct index_base + { + static constexpr util::uuid uuid() + { + return UUID; + } + + index_base(ecs::container & world) + : world_(world) + { + world.apply([this](ecs::handle entity, Component const & v){ + index_[v.location] = entity; + }); + + world.constructor([this](ecs::handle entity, Component const & v){ + index_[v.location] = entity; + }); + + world.destructor([this](Component const & v){ + index_.erase(v.location); + }); + } + + std::optional find(location const & l) const + { + if (auto it = index_.find(l); it != index_.end()) + return it->second; + return std::nullopt; + } + + ecs::handle get(location const & l) const + { + if (auto entity = find(l)) + return *entity; + + return world_.create(Component{l}); + } + + private: + ecs::container & world_; + util::hash_map index_; + }; + + using index = index_base; + using path_index = index_base; + void generate_next_task(random::generator & rng, map & map) { color type; + util::hash_set existing_tasks; + int task_count = 0; + map.world.apply([&](task const & task) + { + existing_tasks.insert(task.type); + task_count += 1; + }); + while (true) { type = random::uniform_from(rng, color_values()); - if (map.tasks.size() >= 3) + if (task_count >= 3) break; - bool good = true; - for (auto const & t : map.tasks) - if (t.second.type == type) - good = false; - - if (good) + if (!existing_tasks.contains(type)) break; } @@ -223,10 +252,15 @@ namespace gmtk int y = random::uniform(rng) ? -1 : 3; if (random::uniform(rng)) std::swap(x, y); - if (map.tasks.contains({x, y})) + + if (map.world.index().find({0, {x, y}})) continue; - map.tasks[{x, y}] = {type}; + map.world.create( + vertex{{0, {x, y}}}, + task{type} + ); + break; } } @@ -235,17 +269,17 @@ namespace gmtk { map result; - result.grid = grid::create(); + result.world.index(); + result.world.index(); generate_next_task(rng, result); return result; } - void draw(map const & map, gfx::painter & painter) + void draw(map & map, gfx::painter & painter) { float const grid_width = 0.1f; - geom::box source_box{{{0.2f, 0.8f}, {0.2f, 0.8f}}}; for (int x = 0; x <= 3; ++x) { @@ -253,71 +287,70 @@ namespace gmtk painter.line({0.f, x}, {3.f, x}, grid_width, gfx::black, true); } - auto & grid = map.grid; - - for (auto p : grid::indices) - for (auto q : grid.belts(p)) - painter.line(geom::cast(p) + geom::vector{0.5f, 0.5f}, geom::cast(q) + geom::vector{0.5f, 0.5f}, 0.3f, {191, 191, 191, 255}, true); - - for (auto p : grid::indices) + map.world.apply([&](path_vertex const & vertex) { - for (auto q : grid.belts(p)) + for (auto b : vertex.belts) { - for (int i = 0; i < 3; ++i) - { - geom::vector d = geom::cast(q - p); - - auto c = geom::cast(p) + geom::vector{0.5f, 0.5f} + d * (i / 3.f); - - auto n = geom::ort(d); - - c += d / 6.f; - - float s = 1.f / 24.f; - - d *= s; - n *= s; - - painter.triangle(c - d + n, c - d - n, c + d, {255, 127, 0, 255}); - } + auto q = map.world.get(b).get().location; + painter.line(vertex.location.center(), q.center(), 0.3f, {191, 191, 191, 255}, true); } - } + }); - for (auto p : grid::indices) + map.world.apply([&](path_vertex const & vertex) { - auto const & cell = grid.tiles(p); - - if (auto source = std::get_if(&cell)) + for (auto b : vertex.belts) { - painter.rect(source_box + geom::vector{p[0] * 1.f, p[1] * 1.f}, to_color(source->type)); - painter.text({p[0] + 0.5f, p[1] + 0.5f}, "180/m", {.scale = {0.01f, -0.01f}, .c = {0, 0, 0, 255}}); - } - else if (auto factory = std::get_if(&cell)) - { - auto box = source_box + geom::vector{p[0] * 1.f, p[1] * 1.f}; - painter.triangle(box.corner(0, 0), box.corner(1, 1), box.corner(0, 1), to_color(factory->input)); - painter.triangle(box.corner(0, 0), box.corner(1, 0), box.corner(1, 1), to_color(factory->output)); - } - } + auto q = map.world.get(b).get().location; - for (auto const & task : map.tasks) - { - painter.rect(source_box + geom::vector{task.first[0] * 1.f, task.first[1] * 1.f}, to_color(task.second.type)); - painter.text(geom::point{task.first[0] + 0.5f, task.first[1] + 0.5f}, std::format("{}/m", task.second.received.size() * task::freq), {.scale = {0.01f, -0.01f}, .c = {0, 0, 0, 255}}); - } + geom::vector d = q.center() - vertex.location.center(); - for (auto const & item : map.items) + auto c = vertex.location.center() + d / 2.f; + + auto n = geom::ort(d); + + float s = 1.f / 6.f; + + d *= s * 0.5f; + n *= s; + + painter.triangle(c - d + n, c - d - n, c + d, {255, 127, 0, 255}); + } + }); + + map.world.apply([&](vertex const & v, source const & s) { - auto pos = geom::lerp(geom::cast(item.start), geom::cast(item.target), item.pos) + geom::vector{0.5f, 0.5f}; - pos[0] /= 3.f; - pos[1] /= 3.f; + painter.rect(geom::shrink(v.location.bbox(), 0.2f), to_color(s.type)); + painter.text(v.location.center(), "180/m", {.scale = {0.01f, -0.01f}, .c = {0, 0, 0, 255}}); + }); + + map.world.apply([&](vertex const & v, transformer const & t) + { + auto box = geom::shrink(v.location.bbox(), 0.2f); + painter.triangle(box.corner(0, 0), box.corner(1, 1), box.corner(0, 1), to_color(t.input)); + painter.triangle(box.corner(0, 0), box.corner(1, 0), box.corner(1, 1), to_color(t.output)); + }); + + map.world.apply([&](vertex const & v, task const & t) + { + painter.rect(geom::shrink(v.location.bbox(), 0.2f), to_color(t.type)); + painter.text(v.location.center(), std::format("{}/m", t.received.size() * task::freq), {.scale = {0.01f, -0.01f}, .c = {0, 0, 0, 255}}); + }); + + map.world.apply([&](item const & i) + { + geom::point pos; + if (i.target) + pos = geom::lerp(i.start.center(), map.world.get(i.target).get().location.center(), i.state); + else + pos = i.start.center(); painter.circle(pos, 0.075f, {0, 0, 0, 255}); - painter.circle(pos, 0.05f, to_color(item.type)); - } + painter.circle(pos, 0.05f, to_color(i.type)); + }); } - void draw_selection(geom::point const & p, gfx::painter & painter) + void draw_selection(location const & l, gfx::painter & painter) { + auto p = l.coords; painter.line({p[0], p[1]}, {p[0] + 1.f, p[1]}, 0.1f, {255, 0, 255, 255}, true); painter.line({p[0] + 1.f, p[1]}, {p[0] + 1.f, p[1] + 1.f}, 0.1f, {255, 0, 255, 255}, true); painter.line({p[0] + 1.f, p[1] + 1.f}, {p[0], p[1] + 1.f}, 0.1f, {255, 0, 255, 255}, true); @@ -346,15 +379,25 @@ namespace gmtk { if (event.down && event.key == app::keycode::S) { - if (selected_ && std::holds_alternative(map_.grid.tiles(*selected_))) + if (selected_ && !map_.world.index().find(*selected_)) { util::hash_set types; - if (map_.tasks.size() == 1) - types.insert(map_.tasks.begin()->second.type); - else + int task_count = 0; + + map_.world.apply([&](task const & t) + { + types.insert(t.type); + task_count += 1; + }); + + if (task_count > 1) for (auto t : color_values()) types.insert(t); - map_.grid.tiles(*selected_) = source{random::uniform_from(rng_, types)}; + + map_.world.create( + vertex{*selected_}, + source{random::uniform_from(rng_, types)} + ); } } @@ -364,16 +407,32 @@ namespace gmtk { if (belt_start_) { - auto d = *selected_ - *belt_start_; + auto d = selected_->coords - belt_start_->coords; if (std::abs(d[0]) + std::abs(d[1]) == 1) { - if (within_grid(*selected_)) - map_.grid.belts(*selected_).erase(*belt_start_); + auto & index = map_.world.index(); + for (int i = 0; i < 3; ++i) + { + auto p = belt_start_->down(); + p.coords += d * i; + auto q = p; + q.coords += d; - if (map_.grid.belts(*belt_start_).contains(*selected_)) - map_.grid.belts(*belt_start_).erase(*selected_); - else - map_.grid.belts(*belt_start_).insert(*selected_); + auto s = index.get(p); + auto t = index.get(q); + + auto & sv = map_.world.get(s).get(); + auto & tv = map_.world.get(t).get(); + + if (sv.belts.contains(t)) + sv.belts.erase(t); + else + { + if (tv.belts.contains(s)) + tv.belts.erase(s); + sv.belts.insert(t); + } + } } belt_start_ = std::nullopt; } @@ -384,13 +443,18 @@ namespace gmtk if (event.down && event.key == app::keycode::F) { - if (selected_ && std::holds_alternative(map_.grid.tiles(*selected_))) + if (selected_ && !map_.world.index().find(*selected_)) { util::hash_set types; - for (auto const & task : map_.tasks) - types.insert(task.second.type); + int task_count = 0; - if (types.size() == 1) + map_.world.apply([&](task const & t) + { + types.insert(t.type); + task_count += 1; + }); + + if (task_count == 1) { for (auto c : color_values()) types.insert(c); @@ -400,7 +464,10 @@ namespace gmtk types.erase(input); color output = random::uniform_from(rng_, types); - map_.grid.tiles(*selected_) = gmtk::factory{input, output}; + map_.world.create( + vertex{*selected_}, + transformer{input, output} + ); } } @@ -408,7 +475,9 @@ namespace gmtk { if (selected_) { - map_.grid.tiles(*selected_) = empty{}; + if (auto entity = map_.world.index().find(*selected_)) + if (!map_.world.get(*entity).contains()) + map_.world.destroy(*entity); } } @@ -439,114 +508,84 @@ namespace gmtk { map_.spawn_timer -= 1.f; - for (auto p : grid::indices) - { - if (auto source = std::get_if(&map_.grid.tiles(p))) + map_.world.apply( + [&](vertex const & v, source const & s) { - auto pos = cell_center_to_item(p); + auto p = v.location.down(); - if (!map_.grid.item_target(pos)) + auto t = map_.world.index().get(p); + + if (!map_.world.get(t).contains()) { - auto & item = map_.items.emplace_back(); - item.type = source->type; - item.start = pos; - item.target = pos; - map_.grid.item_target(pos) = true; + auto i = map_.world.create( + item{s.type, p} + ); + + map_.world.attach(t, occupied{i}); } } - } + ); } - for (auto & task : map_.tasks) + + map_.world.apply([&](task & t) { - auto & received = task.second.received; auto threshold = map_.time; threshold.trunc -= 60 / task::freq; - while (!received.empty() && received.front() < threshold) - received.pop_front(); - } + while (!t.received.empty() && t.received.front() < threshold) + t.received.pop_front(); + }); - std::vector alive_items; - for (auto item : map_.items) + map_.world.apply([&](ecs::handle entity, item & i) { - if (item.start != item.target) + if (i.target) { - item.pos += 3.f * dt; + i.state += 3.f * dt; + if (i.state < 1.f) + return; - if (item.pos < 1.f) - { - alive_items.push_back(item); - continue; - } + i.state -= 1.f; - item.pos -= 1.f; - } - - if (item_within_grid(item.target)) - map_.grid.item_target(item.target) = false; - - auto cell = item_to_cell(item.target); - - if (map_.tasks.contains(cell)) - { - if (map_.tasks.at(cell).type == item.type) - map_.tasks.at(cell).received.push_back(map_.time); - continue; - } - - if (item_within_grid(item.target) && item.target == cell_center_to_item(cell)) - { - if (auto factory = std::get_if(&map_.grid.tiles(cell))) - { - if (factory->input == item.type) - item.type = factory->output; - } - } - - std::vector> targets; - - if (geom::imod(item.target[0], 3) == 1 && geom::imod(item.target[1], 3) == 1) - { - for (auto q : map_.grid.belts(cell)) - { - auto t = item.target + (q - cell); - if (!item_within_grid(t) || !map_.grid.item_target(t)) - targets.push_back(t); - } + i.start = map_.world.get(i.target).get().location; + map_.world.detach(i.target); + i.target = ecs::handle::null(); } else { - auto s = item.target; - if (geom::imod(s[0], 3) == 0) - s[0] -= 1; - else if (geom::imod(s[0], 3) == 2) - s[0] += 1; - else if (geom::imod(s[1], 3) == 0) - s[1] -= 1; - else if (geom::imod(s[1], 3) == 2) - s[1] += 1; - - if (!item_within_grid(s) || !map_.grid.item_target(s)) - if (within_grid(item_to_cell(item.target)) && map_.grid.belts(item_to_cell(item.target)).contains(item_to_cell(s))) - targets.push_back(s); - - auto t = item.target - (s - item.target); - - if (!item_within_grid(t) || !map_.grid.item_target(t)) - if (within_grid(item_to_cell(s)) && map_.grid.belts(item_to_cell(s)).contains(item_to_cell(item.target))) - targets.push_back(t); + map_.world.detach(map_.world.index().get(i.start)); } - item.start = item.target; + if (auto cell = map_.world.index().find(i.start.up())) + { + if (auto t = map_.world.get(*cell).get_if()) + { + if (t->type == i.type) + t->received.push_back(map_.time); + map_.world.destroy(entity); + return; + } + + if (auto t = map_.world.get(*cell).get_if()) + { + if (t->input == i.type) + i.type = t->output; + } + } + + std::vector targets; + + for (auto b : map_.world.get(map_.world.index().get(i.start)).get().belts) + if (!map_.world.get(b).contains()) + targets.push_back(b); if (!targets.empty()) - item.target = random::uniform_from(rng_, targets); + i.target = random::uniform_from(rng_, targets); - if (item_within_grid(item.target)) - map_.grid.item_target(item.target) = true; - alive_items.push_back(item); - } - map_.items = std::move(alive_items); + if (i.target) + map_.world.attach(i.target, occupied{entity}); + else + map_.world.attach(map_.world.index().get(i.start), occupied{entity}); + }); float aspect_ratio = (screen_size_[0] * 1.f) / screen_size_[1]; @@ -563,9 +602,9 @@ namespace gmtk int y = std::floor(m[1]); if (x >= 0 && x < 3 && y >= 0 && y < 3) - selected_ = geom::point{x, y}; - else if (map_.tasks.contains({x, y})) - selected_ = geom::point{x, y}; + selected_ = {0, geom::point{x, y}}; + else if (map_.world.index().find({0, {x, y}})) + selected_ = {0, geom::point{x, y}}; } } @@ -597,8 +636,8 @@ namespace gmtk geom::box view_box_; - std::optional> selected_; - std::optional> belt_start_; + std::optional selected_; + std::optional belt_start_; geom::point screen_to_grid(geom::point const & p) {