Compare commits

...

11 Commits

Author SHA1 Message Date
Connor
cd745f0eda 8x8 grid 2026-02-20 18:24:27 +09:00
Connor
fc3192cd1e cleanup 2026-02-20 18:24:20 +09:00
Connor
c081aa868f support 2026-02-20 14:53:28 +09:00
Connor
cf20ed827e chute 2026-02-16 16:55:17 +09:00
Connor
7ae69ea1ff inventory tests 2026-02-16 11:27:01 +09:00
Connor
01eaebeb71 resource renewal tests 2026-02-15 23:01:19 +09:00
Connor
5534b169d6 health 2026-02-15 22:48:35 +09:00
Connor
c7c679c378 compile error fix 2026-02-15 19:06:20 +09:00
Connor
09102b934b type fix 2026-02-15 18:59:15 +09:00
Connor
22e754cd75 2/15/2026 2026-02-15 18:42:47 +09:00
Connor
524ba9691b 9-2-26 2026-02-09 00:53:38 +09:00
70 changed files with 13013 additions and 159 deletions

1048
.gitignore vendored

File diff suppressed because it is too large Load Diff

9
CLAUDE.md Normal file
View File

@@ -0,0 +1,9 @@
# CLAUDE.md
## Dependencies
### Flecs (ECS Framework)
Documentation is located at `build/flecs-src/docs/`. If the docs are not available, run CMake to fetch and build dependencies:
```
cmake -B build
```

View File

@@ -1,33 +1,66 @@
cmake_minimum_required(VERSION 3.16)
project(factory_hole_core
VERSION 0.1.0
DESCRIPTION "High-performance ECS-based factory game engine core"
LANGUAGES CXX
)
cmake_minimum_required(VERSION 3.20)
project(factory-hole-core LANGUAGES CXX)
# C++17 standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Options
option(FACTORY_CORE_BUILD_TESTS "Build tests" ON)
option(FACTORY_CORE_BUILD_EXAMPLES "Build examples" ON)
# Export compile commands for IDE support
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Include dependency management
include(cmake/FetchDependencies.cmake)
include(FetchContent)
# Add subdirectories
add_subdirectory(src)
# Flecs ECS
FetchContent_Declare(
flecs
GIT_REPOSITORY https://github.com/SanderMertens/flecs.git
GIT_TAG v4.1.4
GIT_SHALLOW TRUE
)
if(FACTORY_CORE_BUILD_TESTS)
# Doctest
FetchContent_Declare(
doctest
GIT_REPOSITORY https://github.com/doctest/doctest.git
GIT_TAG v2.4.12
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(flecs doctest)
# Only compile sources needed for the core library
set(SOURCES
src/Components/Config/WorldConfig.cpp
src/Components/Support.cpp
src/Core/WorldInstance.cpp
)
add_library(factory-hole-core ${SOURCES})
# Main executable
add_executable(factory-hole-app src/main.cpp)
target_link_libraries(factory-hole-app PRIVATE factory-hole-core)
target_include_directories(factory-hole-core PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(factory-hole-core PUBLIC
flecs::flecs_static
doctest::doctest
)
# Tests
enable_testing()
add_subdirectory(tests)
endif()
if(FACTORY_CORE_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
file(GLOB_RECURSE TEST_SOURCES tests/*.cpp)
add_executable(factory-hole-tests ${TEST_SOURCES})
target_include_directories(factory-hole-tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(factory-hole-tests PRIVATE
factory-hole-core
doctest::doctest_with_main
)
add_test(NAME factory-hole-tests COMMAND factory-hole-tests)

View File

@@ -1,20 +0,0 @@
include(FetchContent)
# Fetch flecs - high-performance ECS library
FetchContent_Declare(
flecs
GIT_REPOSITORY https://github.com/SanderMertens/flecs.git
GIT_TAG v4.0.4
GIT_SHALLOW TRUE
)
# Fetch doctest - testing framework (same as Godot uses)
FetchContent_Declare(
doctest
GIT_REPOSITORY https://github.com/doctest/doctest.git
GIT_TAG v2.4.11
GIT_SHALLOW TRUE
)
# Make dependencies available
FetchContent_MakeAvailable(flecs doctest)

View File

@@ -0,0 +1,148 @@
#pragma once
#include <deque>
#include <cmath>
#include <algorithm>
#include <vector>
#include "flecs.h"
#include "Types/Item.hpp"
#include "Components/Misc.hpp"
#include "Components/Inventory.hpp"
#include "Util/SharedBuffer.h"
struct ChuteConfig
{
float Gravity{ 1.0f };
float MinSpeed{ 0.5f };
};
struct ChuteInventoryInput : public Inventory {};
struct ChuteInventoryOutput : public Inventory {};
struct Chute
{
struct ChuteLink
{
int8_t RelativeX{};
int8_t RelativeY{};
uint16_t Tick{};
};
struct ChuteItem
{
Item ItemInfo{};
uint16_t ChuteEntered{}; // use TickCounter - ChuteEntered to get the time it has been in the chute
};
struct ChuteData
{
std::deque<ChuteItem> ItemsInChute{};
uint16_t TicksToReachEnd{};
uint16_t TickCounter{};
};
public:
Chute() = default;
Chute(const std::vector<Vector2>& positions, const ChuteConfig& config = {})
: Data{static_cast<int>(positions.size() - 1), ChuteData{}}
{
float velocity = 0.0f;
float totalTicks = 0.0f;
for (size_t i = 1; i < positions.size(); ++i)
{
int dx = positions[i].X - positions[i - 1].X;
int dy = positions[i].Y - positions[i - 1].Y;
// accumulate velocity from vertical drop (dy <= 0, so -dy >= 0)
velocity += config.Gravity * static_cast<float>(std::abs(dy));
velocity = std::max(velocity, config.MinSpeed);
float distance = std::max(1.0f, std::sqrt(static_cast<float>(dx * dx + dy * dy)));
float linkTicks = distance / velocity;
Data[i - 1].RelativeX = static_cast<int8_t>(dx);
Data[i - 1].RelativeY = static_cast<int8_t>(dy);
Data[i - 1].Tick = static_cast<uint16_t>(std::ceil(linkTicks));
totalTicks += linkTicks;
}
Data.GetMetaData()->TicksToReachEnd = static_cast<uint16_t>(std::ceil(totalTicks));
}
void PushItem(Item item)
{
auto* meta = Data.GetMetaData();
meta->ItemsInChute.push_back(ChuteItem{ .ItemInfo = item, .ChuteEntered = meta->TickCounter });
}
public:
SharedBuffer<ChuteLink, ChuteData> Data{};
};
inline void Flecs_Chute(flecs::world& world)
{
world.component<ChuteInventoryInput>().is_a<Inventory>();
world.component<ChuteInventoryOutput>().is_a<Inventory>();
world.component<ChuteConfig>()
.member<float>("Gravity")
.member<float>("MinSpeed");
world.component<Chute>();
// tick the chute counter
world.system<Chute>("Chute Tick")
.kind(flecs::PreUpdate)
.each([](Chute& chute) {
chute.Data.GetMetaData()->TickCounter++;
});
// pull items from input inventory into chute
world.system<Chute, ChuteInventoryInput>("Chute Input")
.kind(flecs::OnUpdate)
.each([](Chute& chute, ChuteInventoryInput& input) {
for (uint16_t i = 0; i < input.Slots.GetSize(); ++i)
{
while (input.Slots[i] > 0)
{
input.Slots[i]--;
chute.PushItem(Item{ i });
}
}
});
// pop items that have reached the end and deposit into output inventory
world.system<Chute, ChuteInventoryOutput, WorldInventory>("Chute Output")
.kind(flecs::OnUpdate)
.each([](Chute& chute, ChuteInventoryOutput& output, WorldInventory& worldInv) {
auto* meta = chute.Data.GetMetaData();
while (!meta->ItemsInChute.empty())
{
auto& front = meta->ItemsInChute.front();
uint16_t elapsed = meta->TickCounter - front.ChuteEntered;
if (elapsed < meta->TicksToReachEnd)
break;
uint16_t itemID = front.ItemInfo.ItemID;
meta->ItemsInChute.pop_front();
if (!output.IsFull(itemID))
output.AddItems(itemID, 1);
else
worldInv.AddItems(itemID, 1);
}
});
}
inline void Chute_Helper(const flecs::entity& entity,
const std::vector<Vector2>& positions,
const Inventory& sourceInventory,
const Inventory& destInventory,
const ChuteConfig& config = {})
{
entity.set<Chute>(Chute{positions, config});
entity.set<ChuteInventoryInput>(ChuteInventoryInput{sourceInventory});
entity.set<ChuteInventoryOutput>(ChuteInventoryOutput{destInventory});
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include <unordered_map>
#include <vector>
#include <string>
#include "Types/Item.hpp"
struct ItemConfig
{
Item ItemID{};
std::string Name{};
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include <vector>
#include "ItemConfig.hpp"
struct RecipeConfig
{
std::vector<ItemAmount32> Ingredients{};
std::vector<ItemAmount32> Results{};
float ProcessingTime{};
uint32_t ID{ std::numeric_limits<uint32_t>::max() };
};

View File

@@ -0,0 +1,55 @@
#pragma once
#include <vector>
#include <string>
#include "Util/Span.h"
#include "flecs.h"
#include "ItemConfig.hpp"
#include "RecipeConfig.hpp"
#include "Components/Misc.hpp"
class WorldConfig
{
public:
uint16_t RegisterItem(const std::string& name);
public:
tcb::span<const ItemConfig> GetItems() const { return Items; }
private:
std::vector<ItemConfig> Items{};
std::vector<RecipeConfig> Recipes{};
};
inline void Flecs_Configs(flecs::world& world)
{
// std::string opaque support
world.component<std::string>()
.opaque(flecs::String)
.serialize([](const flecs::serializer *s, const std::string *data) {
const char *str = data->c_str();
return s->value(flecs::String, &str);
})
.assign_string([](std::string *data, const char *value) {
*data = value;
});
// std::vector<ItemAmount32> opaque support
world.component<std::vector<ItemAmount32>>()
.opaque(std_vector_support<ItemAmount32>);
// ItemConfig
world.component<ItemConfig>()
.member<Item>("ItemID")
.member<std::string>("Name");
// RecipeConfig
world.component<RecipeConfig>()
.member<std::vector<ItemAmount32>>("Ingredients")
.member<std::vector<ItemAmount32>>("Results")
.member<float>("ProcessingTime")
.member<uint32_t>("ID");
}

View File

@@ -0,0 +1,228 @@
#pragma once
#include <array>
#include "flecs.h"
#include "Types/Item.hpp"
#include "Util/SharedBuffer.h"
#include "Util/Span.h"
template <typename IntegralType>
struct InventoryT
{
struct InventoryMeta
{
IntegralType MaxSize{ std::numeric_limits<IntegralType>::max() };
};
public:
InventoryT() = default;
InventoryT(size_t itemAmount) { Slots = { static_cast<int>(itemAmount), InventoryMeta{} }; }
InventoryT(size_t itemAmount, IntegralType maxAmount) { Slots = { static_cast<int>(itemAmount), InventoryMeta{ maxAmount } }; }
public:
IntegralType GetItemsAmount(uint16_t id) const { Assert(id); return Slots[id]; }
IntegralType GetItemsAmount(Item item) const { return GetItemsAmount(item.ItemID); }
void RemoveItems(uint16_t id, IntegralType amount) { Assert(id); Slots[id]-= amount; }
void RemoveItems(Item id, IntegralType amount) { RemoveItems(id.ItemID, amount); }
template <typename OtherIntegralType>
void RemoveItems(ItemAmountT<OtherIntegralType> amount) { RemoveItems(amount.Item, amount.Amount); }
void AddItems(uint16_t id, IntegralType amount) { Assert(id); Slots[id]+= amount; }
void AddItems(Item id, IntegralType amount) { AddItems(id.ItemID, amount); }
template <typename OtherIntegralType>
void AddItems(ItemAmountT<OtherIntegralType> item) { AddItems(item.Item.ItemID, item.Amount); }
template <typename OtherIntegralType>
void AddItems(const InventoryT<OtherIntegralType> other)
{
DEV_ASSERT(Slots.GetSize() == other.Slots.GetSize());
for (uint32_t i{}; i < Slots.GetSize(); ++i)
Slots[i] += other.Slots[i];
}
bool IsFull(uint16_t id) const { Assert(id); return Slots[id] >= Slots.GetMetaData()->MaxSize; }
void Clear()
{
for (IntegralType i{}; i < Slots.GetSize(); ++i)
Slots[i] = 0;
}
void Assert(uint16_t id) const
{
DEV_ASSERT(id < Slots.GetSize() && id != Item::null);
}
public:
operator bool() const { return Slots; }
public:
SharedBuffer<IntegralType, InventoryMeta> Slots;
};
typedef InventoryT<uint16_t> Inventory16;
typedef InventoryT<uint32_t> Inventory32;
typedef InventoryT<uint64_t> Inventory64;
typedef Inventory32 Inventory;
typedef Inventory64 WorldInventory;
struct FixedInventoryEntry
{
FixedInventoryEntry() = default;
FixedInventoryEntry(Item item, uint16_t amount, uint16_t maxAmount) : ItemInfo{ item }, Amount{ amount }, MaxAmount{ maxAmount } {};
Item ItemInfo{};
uint16_t Amount{};
uint16_t MaxAmount{std::numeric_limits<uint16_t>::max()};
};
template <uint8_t Size>
struct FixedInventoryBase
{
FixedInventoryBase() = default;
template <typename It>
FixedInventoryBase(const It& it)
{
int counter{};
for (const auto& val : it)
Data[counter++] = val;
}
tcb::span<FixedInventoryEntry> GetInventoryData()
{
return {&Data[0], InventorySize};
}
tcb::span<const FixedInventoryEntry> GetInventoryData() const
{
return {&Data[0], InventorySize};
}
uint8_t InventorySize{ Size };
std::array<FixedInventoryEntry, Size> Data{};
};
typedef FixedInventoryBase<1> FixedInventory1;
typedef FixedInventoryBase<2> FixedInventory2;
typedef FixedInventoryBase<3> FixedInventory3;
typedef FixedInventoryBase<4> FixedInventory4;
typedef FixedInventoryBase<5> FixedInventory5;
typedef FixedInventoryBase<6> FixedInventory6;
typedef FixedInventoryBase<7> FixedInventory7;
typedef FixedInventoryBase<8> FixedInventory8;
struct InventoryAreaOfEffect
{
InventoryAreaOfEffect(bool isCircle, uint8_t size)
: IsCircle{ isCircle }
, ShapeSize{ size }
{
}
uint8_t IsCircle : 1;
uint8_t ShapeSize : 7;
};
static_assert(sizeof(InventoryAreaOfEffect) == 1);
struct InventoryOwner
{};
struct ItemProcessor
{
ItemProcessor() = default;
ItemProcessor(uint32_t ticks) : ProcessedTicks{ ticks } {};
uint32_t ProcessedTicks;
};
inline void Flecs_Inventory(flecs::world& world)
{
world.component<FixedInventoryEntry>()
.member<Item>("Item")
.member<uint16_t>("Amount")
.member<uint16_t>("MaxAmount");
auto fixedInv1 = world.component<FixedInventory1>();
fixedInv1.add(flecs::Inheritable);
fixedInv1
.opaque(world.vector<FixedInventoryEntry>())
.serialize([](const flecs::serializer *s, const FixedInventory1 *data) {
for (uint8_t i = 0; i < data->InventorySize; ++i)
s->value(data->Data[i]);
return 0;
})
.count([](const FixedInventory1 *data) -> size_t {
return data->InventorySize;
})
.ensure_element([](FixedInventory1 *data, size_t elem) -> void* {
return &data->Data[elem];
});
world.component<FixedInventory2>().is_a<FixedInventory1>();
world.component<FixedInventory3>().is_a<FixedInventory1>();
world.component<FixedInventory4>().is_a<FixedInventory1>();
world.component<FixedInventory5>().is_a<FixedInventory1>();
world.component<FixedInventory6>().is_a<FixedInventory1>();
world.component<FixedInventory7>().is_a<FixedInventory1>();
world.component<FixedInventory8>().is_a<FixedInventory1>();
auto inv = world.component<Inventory>();
inv.add(flecs::Inheritable);
inv
.opaque(world.vector<uint32_t>())
.serialize([](const flecs::serializer *s, const Inventory *data) {
if (!data->Slots) return 0;
for (uint32_t i = 0; i < data->Slots.GetSize(); ++i)
s->value(data->Slots[i]);
return 0;
})
.count([](const Inventory *data) -> size_t {
if (!data->Slots) return 0;
return data->Slots.GetSize();
});
auto worldInv = world.component<WorldInventory>();
worldInv.add(flecs::Singleton);
worldInv
.opaque(world.vector<uint64_t>())
.serialize([](const flecs::serializer *s, const WorldInventory *data) {
if (!data->Slots) return 0;
for (uint64_t i = 0; i < data->Slots.GetSize(); ++i)
s->value(data->Slots[i]);
return 0;
})
.count([](const WorldInventory *data) -> size_t {
if (!data->Slots) return 0;
return data->Slots.GetSize();
});
world.component<InventoryAreaOfEffect>()
.opaque(world.component()
.member<uint8_t>("IsCircle")
.member<uint8_t>("Size"))
.serialize([](const flecs::serializer *s, const InventoryAreaOfEffect *data) {
uint8_t isCircle = data->IsCircle;
uint8_t size = data->ShapeSize;
s->member("IsCircle");
s->value(isCircle);
s->member("Size");
s->value(size);
return 0;
});
world.component<ItemProcessor>()
.member<uint32_t>("ProcessedTicks");
world.component<InventoryOwner>();
}
inline void Inventory_Helper(const flecs::entity& entity, const WorldConfig& config, uint32_t maxPerSlot)
{
entity.set<Inventory>(Inventory{config.GetItems().size(), maxPerSlot});
entity.add<InventoryOwner>();
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include "flecs.h"
#include <vector>
struct Vector2
{
Vector2() = default;
Vector2(int32_t x, int32_t y): X{ x }, Y{ y } {};
int32_t X{}, Y{};
Vector2 operator+(const Vector2& other) const { return { X + other.X, Y + other.Y }; }
Vector2 operator-(const Vector2& other) const { return { X - other.X, Y - other.Y }; }
};
struct TilePosition
{
Vector2 Position;
};
struct Bounds
{
Vector2 Min;
Vector2 Max;
};
struct Level
{
Level() = default;
Level(uint8_t level) : Val{ level } {};
uint8_t Val;
};
template <typename Elem, typename Vector = std::vector<Elem>>
flecs::opaque<Vector, Elem> std_vector_support(flecs::world& world) {
return flecs::opaque<Vector, Elem>()
.as_type(world.vector<Elem>())
.serialize([](const flecs::serializer *s, const Vector *data) {
for (const auto& el : *data)
s->value(el);
return 0;
})
.count([](const Vector *data) {
return data->size();
})
.resize([](Vector *data, size_t size) {
data->resize(size);
})
.ensure_element([](Vector *data, size_t elem) {
if (data->size() <= elem)
data->resize(elem + 1);
return &data->data()[elem];
});
}
inline void Flecs_Misc(flecs::world& world)
{
world.component<Vector2>()
.member<int32_t>("x")
.member<int32_t>("y");
world.component<TilePosition>()
.member<Vector2>("Position");
world.component<Bounds>()
.member<Vector2>("Min")
.member<Vector2>("Max");
world.component<Level>()
.member<uint8_t>("Val");
}

View File

@@ -0,0 +1,127 @@
#pragma once
#include <stdint.h>
#include "flecs.h"
#include "Tick.hpp"
#include "Inventory.hpp"
struct ResourceInfo
{
uint16_t ResourceID;
};
struct ResourceHealth
{
uint16_t MaxHealth{};
uint16_t Health{};
};
struct ResourceTick : public TickAccumulator
{};
struct Renewing
{};
struct RenewingTick : public TickAccumulator
{};
struct FullyGrown
{};
inline void Flecs_Resource(flecs::world& world)
{
world.component<ResourceInfo>()
.member<uint16_t>("ResourceID");
world.component<ResourceHealth>()
.member<uint16_t>("MaxHealth")
.member<uint16_t>("Health");
world.component<ResourceTick>()
.is_a<TickAccumulator>();
world.component<RenewingTick>()
.is_a<TickAccumulator>();
world.component<Renewing>()
.add<Freezes, ResourceTick>();
// harvesting resource to inventory
world.system<const ResourceInfo, const ResourceTick, WorldInventory, Inventory*>()
.kind(flecs::OnUpdate)
.without<Renewing>()
.each([](ResourceInfo info, ResourceTick tick, WorldInventory& worldInventory, Inventory* optionalInventory) {
if (tick.Finished())
{
bool pushToLocalInventory = optionalInventory && !optionalInventory->IsFull(info.ResourceID);
if (pushToLocalInventory) optionalInventory->AddItems(info.ResourceID, 1);
else worldInventory.AddItems(info.ResourceID, 1);
}
});
// decrease health if ResourceHealth component
world.system<ResourceHealth, const ResourceTick>()
.kind(flecs::OnUpdate)
.without<Renewing>()
.each([](ResourceHealth& health, ResourceTick tick){
health.Health -= tick.Finished();
});
// checking if we have to renew the resource
world.system<const ResourceHealth>()
.kind(flecs::OnUpdate)
.with<RenewingTick>()
.without<Renewing>()
.each([](flecs::entity entity, ResourceHealth health) {
if (health.Health == 0) {
entity.remove<FullyGrown>();
entity.add<Renewing>();
entity.ensure<RenewingTick>().AccumulatedTick = 0;
}
});
// finish renewing
world.system<ResourceHealth, RenewingTick>("Finish Renewing")
.kind(flecs::OnUpdate)
.with<Renewing>()
.each([](flecs::entity entity, ResourceHealth& health, RenewingTick& tick) {
if (tick.Finished()) {
health.Health = health.MaxHealth;
entity.remove<Renewing>();
entity.add<FullyGrown>();
entity.ensure<ResourceTick>().AccumulatedTick = 0;
}
});
}
inline void Resource_Ore_Helper(const flecs::entity& entity, uint16_t resourceID, uint16_t gatherTicks)
{
ResourceInfo info{};
info.ResourceID = resourceID;
ResourceTick tick{};
tick.MaxTick = gatherTicks;
entity.set<ResourceInfo>(info);
entity.set<ResourceTick>(tick);
}
inline void Resource_Tree_Helper(const flecs::entity& entity, uint16_t resourceID,
uint16_t gatherTicks, uint16_t maxHealth, uint16_t renewalTicks)
{
Resource_Ore_Helper(entity, resourceID, gatherTicks);
ResourceHealth health{};
health.MaxHealth = maxHealth;
health.Health = maxHealth;
RenewingTick tick{};
tick.MaxTick = renewalTicks;
entity.set<ResourceHealth>(health);
entity.set<RenewingTick>(tick);
entity.add<FullyGrown>();
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <stdint.h>
#include "flecs.h"
#include "Components/Misc.hpp"
#include "Util/Span.h"
struct Support
{
uint8_t MaxSupport{};
uint8_t SupportsAvailable{};
};
struct GroundedSupport {};
struct RequiresSupport {};
flecs::entity GetSupport(flecs::world& world, Vector2 pos);
void RecalculateSupport(flecs::world& world,
tcb::span<const Vector2> skip = {},
tcb::span<const Vector2> unground = {});
bool CanRemove(flecs::world& world, tcb::span<const Vector2> positions);
void Flecs_Support(flecs::world& world);
inline void Support_Helper(const flecs::entity& entity, Vector2 pos, uint8_t maxSupport, bool grounded = false)
{
entity.set<TilePosition>({ pos });
if (grounded) entity.add<GroundedSupport>(); // before set<Support> so OnAdd fires with grounded state visible
entity.set<Support>({ maxSupport });
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <cstdint>
#include "flecs.h"
struct TickAccumulator
{
uint16_t MaxTick{1};
uint16_t AccumulatedTick;
constexpr bool Finished() const
{
return AccumulatedTick >= MaxTick;
}
};
struct Freezes
{};
inline void Flecs_Tick(flecs::world& world)
{
world.component<TickAccumulator>()
.member<uint16_t>("MaxTick")
.member<uint16_t>("AccumulatedTick")
.add(flecs::Inheritable);
world.component<Freezes>();
world.system<TickAccumulator>("Tick Increase")
.kind(flecs::PreUpdate)
.without<Freezes, TickAccumulator>()
.each([] (TickAccumulator& tick)
{
++tick.AccumulatedTick;
});
world.system<TickAccumulator>("Tick Reset")
.kind(flecs::PostUpdate)
.without<Freezes, TickAccumulator>()
.each([] (TickAccumulator& tick)
{
tick.AccumulatedTick -= tick.MaxTick * tick.Finished();
});
}

184
include/Core/Chunk.h Normal file
View File

@@ -0,0 +1,184 @@
#pragma once
#include <stdint.h>
#include <array>
#include <memory>
#include <vector>
#include <unordered_map>
#include "Util/Span.h"
#include "config.h"
#include "Types/Tile.h"
#include "Components/Misc.hpp"
struct Chunk
{
public:
typedef uint8_t CoordinateType;
static constexpr int ChunkSizePowerOfTwo = 6;
static constexpr int ChunkSize = 1 << ChunkSizePowerOfTwo;
static constexpr int TotalChunkTiles = ChunkSize * ChunkSize;
static constexpr int ChunkMask = (1 << ChunkSizePowerOfTwo) - 1;
static_assert(sizeof(CoordinateType) * 8 >= ChunkSizePowerOfTwo, "CoordinateType is too small to support chunk size");
static constexpr uint8_t WorldToLocal(int world) { return world & ChunkMask; }
std::array<Tile, TotalChunkTiles> Tiles;
public:
static void Assert(int x, int y) { DEV_ASSERT(x < ChunkSize && x >= 0 && y < ChunkSize && y >= 0); }
public:
Tile GetTile(int x, int y) const { Assert(x, y); return Tiles[y * ChunkSize + x]; }
Tile GetTile(Vector2 pos) const { return GetTile(pos.X, pos.Y); }
const Tile& GetTileRef(int x, int y) const { Assert(x, y); return Tiles[y * ChunkSize + x]; }
Tile& GetTile(int x, int y) { Assert(x, y); return Tiles[y * ChunkSize + x]; }
Tile& GetTile(Vector2 pos) { return GetTile(pos.X, pos.Y); }
};
struct ChunkCoordinate
{
ChunkCoordinate() = default;
ChunkCoordinate(int x, int y) : X{ static_cast<uint8_t>(x) }, Y{ static_cast<uint8_t>(y) } { DEV_ASSERT(x >= 0 && x < Chunk::ChunkSize && y >= 0 && y < Chunk::ChunkSize); }
ChunkCoordinate(Vector2 pos) : ChunkCoordinate{pos.X, pos.Y} {}
uint8_t X{};
uint8_t Y{};
operator Vector2() const { return Vector2(X, Y); }
};
static_assert(sizeof(ChunkCoordinate) / 2 >= Chunk::ChunkSizePowerOfTwo / 8);
struct EntityTile final
{
public:
EntityTile() = default;
EntityTile(flecs::entity entity, int worldX, int worldY)
: Entity{ entity }
, ChunkX{ Chunk::WorldToLocal(worldX) }
, ChunkY{ Chunk::WorldToLocal(worldY) }
{}
public:
flecs::entity Entity{};
Chunk::CoordinateType ChunkX{};
Chunk::CoordinateType ChunkY{};
};
struct ChunkKey
{
public:
static constexpr int16_t WorldToChunk(int pos) { return static_cast<int16_t>(pos >> Chunk::ChunkSizePowerOfTwo); }
static constexpr int32_t ChunkToWorld(int16_t pos) { return pos << Chunk::ChunkSizePowerOfTwo; }
public:
int16_t X{}, Y{};
public:
ChunkKey() = default;
ChunkKey(int WorldX, int WorldY) : X{ WorldToChunk(WorldX) }, Y{ WorldToChunk(WorldY) } {}
public:
uint64_t hash64() const { static_assert(sizeof(uint32_t) == sizeof(ChunkKey)); return std::hash<uint32_t>{}(*reinterpret_cast<const uint32_t*>(this)); }
uint32_t hash() const { return static_cast<uint32_t>(hash64()); }
static uint32_t hash(ChunkKey key) { return key.hash(); }
Bounds SetBounds() const { return Bounds{Vector2{ChunkToWorld(X), ChunkToWorld(Y)}, Vector2{ChunkToWorld(X + 1), ChunkToWorld(Y + 1)}}; }
public:
bool operator==(const ChunkKey& rhs) const { return hash() == rhs.hash(); }
};
static_assert(sizeof(ChunkKey) == 4);
struct ChunkData
{
public:
void MarkAsPersistant(flecs::entity entity);
void RemovePersistance(flecs::entity entity);
void Clear()
{
Chunk = {};
Entities.resize(0);
}
public:
std::unique_ptr<Chunk> Chunk{};
std::vector<EntityTile> Entities{};
std::vector<EntityTile> PersistantEntities{};
};
struct ChunkCollection
{
public:
const Chunk& GetChunk(int x, int y);
const Chunk& GetChunk(ChunkKey key);
Chunk const* TryGetChunk(int x, int y) const;
Chunk const* TryGetChunk(ChunkKey key) const;
ChunkData& GetChunkData(int x, int y);
ChunkData& GetChunkData(ChunkKey key);
Tile GetTile(int x, int y);
Tile const* TryGetTile(int x, int y) const;
flecs::entity GetEntity(int x, int y) const;
void SetChunkTiles(int x, int y, std::unique_ptr<Chunk>&& chunk);
void SetChunkTiles(ChunkKey key, std::unique_ptr<Chunk>&& chunk);
void AddEntity(flecs::entity entity, tcb::span<Vector2> claimedPositions);
void AddPersistantEntity(flecs::entity entity, tcb::span<Vector2> claimedPositions);
void MarkAsPersistant(flecs::entity entity);
void RemovePersistance(flecs::entity entity);
void RemoveEntity(flecs::entity entity);
void RemoveChunk(int x, int y);
void RemoveChunk(ChunkKey key);
private:
int GetChunkIndex(int x, int y);
int GetChunkIndex(ChunkKey key);
int TryGetChunkIndex(int x, int y) const;
int TryGetChunkIndex(ChunkKey key) const;
Chunk& GetChunkInternal(int x, int y);
Chunk& GetChunkInternal(ChunkKey key);
Tile& GetTileInternal(int x, int y);
void SetTile(Tile tile, int x, int y);
void InvalidateCachedChunk();
private:
std::vector<ChunkData> ChunkDatas;
std::unordered_map<ChunkKey, int, ChunkKey> ChunkMap{};
ChunkKey CachedChunkKey{};
int CachedChunk{-1};
};
// struct LightValue
// {
// constexpr static uint8_t LightLevelBits = 5;
// constexpr static uint8_t PenetrationBits = 8 - LightLevelBits;
// constexpr static uint8_t MaxLightVal = (1 << LightLevelBits) - 1;
// constexpr static uint8_t MaxPenetration = (1 << PenetrationBits) - 1;
// uint8_t Penetration : PenetrationBits;
// uint8_t Val : LightLevelBits;
// LightValue()
// {
// Penetration = 1;
// Val = 0;
// }
// };
//static_assert(sizeof(LightValue) == 1);
// struct LightChunk
// {
// std::array<LightValue, Chunk::TotalChunkTiles> Tiles;
// public:
// LightValue GetTile(int x, int y) const { Chunk::Assert(x, y); return Tiles[y * Chunk::ChunkSize + x]; }
// LightValue& GetTile(int x, int y) { Chunk::Assert(x, y); return Tiles[y * Chunk::ChunkSize + x]; }
// };

View File

@@ -0,0 +1,197 @@
// #pragma once
// #include "Util/StackAllocator.h"
// #include "Util/Span.h"
// #include "EnTT/entity/registry.hpp"
// #include <functional>
// #include <mutex>
// #include "Components/Sync.h"
// #include "Core/FactoryWorld.h"
// struct FactoryCommand
// {
// void* Data;
// entt::entity Entity;
// void(*Command)(FactoryWorld& world, entt::entity entity, void* data);
// };
// class FactoryCommandQueue final
// {
// static constexpr int DefaultAllocatorSize = 1024 * 1024;
// public:
// FactoryCommandQueue(size_t memorySize)
// : Allocator{ memorySize }
// {}
// FactoryCommandQueue()
// : Allocator{ DefaultAllocatorSize }
// {}
// public:
// public:
// template <typename T>
// void SetComponentData(entt::entity entity, const T& component);
// template <typename T>
// void SetOrAddComponentData(entt::entity entity, const T& component);
// template <typename T>
// void AddIfNone(entt::entity entity, const T& component);
// template <typename T>
// void AddComponent(entt::entity entity, const T& component);
// template <typename T>
// void AddComponent(entt::entity entity);
// template <typename T>
// void RemoveComponent(entt::entity entity);
// //void RemoveEntity(entt::entity entity);
// void SyncEntity(entt::entity entity) { AddComponent<Sync>(entity); }
// void StopSyncingEntity(entt::entity entity) { RemoveComponent<Sync>(entity); }
// void ExecuteAll(FactoryWorld& world);
// void Clear();
// template <typename T>
// T* AllocateData()
// {
// static_assert(std::is_trivially_copyable_v<T>);
// return Allocator.allocate<T>(1);
// }
// template <typename T, typename ... Args>
// T* AllocateData(Args... arguments)
// {
// static_assert(std::is_trivially_copyable_v<T>);
// T* data = AllocateData<T>();
// new (data) T(arguments...);
// return data;
// }
// template <typename T>
// tcb::span<T>* AllocateBuffer(uint32_t size)
// {
// static_assert(std::is_trivially_copyable_v<T>);
// auto spanData = Allocator.allocate<tcb::span<T>>();
// auto data = Allocator.allocate<T>(size);
// for (uint32_t i{}; i < size; ++i)
// data[i] = {};
// *spanData = tcb::span<T>(data, size);
// return spanData;
// }
// private:
// void ClearUnsafe()
// {
// Commands.clear();
// Allocator.reset();
// }
// private:
// StackAllocator Allocator;
// std::mutex Mutex;
// std::vector<FactoryCommand> Commands;
// };
// template<typename T>
// inline void FactoryCommandQueue::SetComponentData(entt::entity entity, const T& component)
// {
// FactoryCommand command;
// command.Entity = entity;
// command.Data = AllocateData<T>(component);
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
// {
// registry.get<T>(entity) = *static_cast<T*>(data);
// };
// std::scoped_lock lock(Mutex);
// Commands.push_back(command);
// }
// template<typename T>
// inline void FactoryCommandQueue::SetOrAddComponentData(entt::entity entity, const T& component)
// {
// FactoryCommand command;
// command.Entity = entity;
// command.Data = AllocateData<T>(component);
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
// {
// registry.emplace_or_replace<T>(entity, *static_cast<T*>(data));
// };
// std::scoped_lock lock(Mutex);
// Commands.push_back(command);
// }
// template<typename T>
// inline void FactoryCommandQueue::AddIfNone(entt::entity entity, const T& component)
// {
// FactoryCommand command;
// command.Entity = entity;
// command.Data = AllocateData<T>(component);
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
// {
// if (!registry.all_of<T>(entity))
// registry.emplace<T>(entity, *static_cast<T*>(data));
// };
// std::scoped_lock lock(Mutex);
// Commands.push_back(command);
// }
// template<typename T>
// inline void FactoryCommandQueue::AddComponent(entt::entity entity, const T& component)
// {
// FactoryCommand command;
// command.Entity = entity;
// command.Data = AllocateData<T>(component);
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
// {
// registry.emplace<T>(entity, *static_cast<T*>(data));
// };
// std::scoped_lock lock(Mutex);
// Commands.push_back(command);
// }
// template<typename T>
// inline void FactoryCommandQueue::AddComponent(entt::entity entity)
// {
// FactoryCommand command;
// command.Entity = entity;
// command.Data = nullptr;
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
// {
// registry.emplace<T>(entity);
// };
// std::scoped_lock lock(Mutex);
// Commands.push_back(command);
// }
// template<typename T>
// inline void FactoryCommandQueue::RemoveComponent(entt::entity entity)
// {
// FactoryCommand command;
// command.Entity = entity;
// command.Data = nullptr;
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
// {
// registry.remove<T>(entity);
// };
// std::scoped_lock lock(Mutex);
// Commands.push_back(command);
// }
// // inline void FactoryCommandQueue::RemoveEntity(entt::entity entity)
// // {
// // FactoryCommand command;
// // command.Entity = entity;
// // command.Data = nullptr;
// // command.Command = [](entt::registry& registry, entt::entity entity, void* data)
// // {
// // registry.destroy(entity);
// // };
// // std::scoped_lock lock(Mutex);
// // Commands.push_back(command);
// // }

159
include/Core/FactoryWorld.h Normal file
View File

@@ -0,0 +1,159 @@
// #pragma once
// #include <array>
// #include "core/templates/vector.h"
// #include "core/templates/hash_map.h"
// #include "core/object/ref_counted.h"
// #include "modules/noise/fastnoise_lite.h"
// #include "core/io/resource.h"
// #include "scene/2d/tile_map_layer.h"
// #include "core/os/mutex.h"
// #include "core/templates/hash_set.h"
// #include "core/math/vector2i.h"
// #include "Components/Position.h"
// #include "Components/Inventory.h"
// #include "Components/Support.h"
// #include "Data/Tile.h"
// #include "Util/ResourceAccess.h"
// #include "Chunk.h"
// #include "SystemBase.h"
// #include "Data/WorldSettings.h"
// #include "FactoryCommandQueue.h"
// #include "WorldThreadData.h"
// class FactoryWorld;
// class FactoryWorldInterface;
// // typedef ResourceAccess<FactoryWorld, Mutex> FactoryWorldAccess;
// enum FactoryError
// {
// FACTORY_ERROR_NONE = 0,
// FACTORY_ERROR_NOT_ENTITY,
// FACTORY_ERROR_CANT_UPGRADE,
// FACTORY_ERROR_FULLY_UPGRADED,
// FACTORY_ERROR_NOT_ENOUGH_ITEMS,
// FACTORY_ERROR_NO_SPACE,
// FACTORY_ERROR_REQUIRES_SUPPORT,
// FACTORY_ERROR_PATH_TOO_LONG,
// FACTORY_ERROR_INVALID_PATH,
// FACTORY_ERROR_INVALID_POS,
// FACTORY_ERROR_MISC_ERROR,
// };
// struct ChunkUnlock final
// {
// ChunkKey ChunkID{};
// Vector<ItemAmount64> Items{};
// };
// class FactoryWorld final
// {
// friend class WorldSerializer;
// friend class WorldLoader;
// public:
// FactoryWorld() = default;
// ~FactoryWorld() = default;
// public:
// void Tick(int amount = 1);
// void Initialize(FactoryWorldInterface* worldInterface);
// void Save();
// public:
// int32_t GetSeed() const { return Seed; }
// FactoryError CanPlaceEntity(int x, int y, Ref<Archetype> archetype);
// FactoryError AddEntity(int x, int y, Ref<Archetype> archetype);
// void RemoveEntity(FactoryEntity* node);
// void RemoveEntity(int x, int y);
// void RemoveEntity(entt::entity entity);
// // FactoryWorldAccess GetAccess() { return FactoryWorldAccess{ *this, WorldAccessMutex }; }
// entt::registry& GetRegistry() { return Registry; };
// const entt::registry& GetRegistry() const { return Registry; };
// auto& GetChunks() { return Chunks; }
// const auto& GetChunks() const { return Chunks; }
// auto& GetSettings() const { return WorldSettings; }
// bool IsValidCameraPos(Rect2i viewport) const;
// public: // Chunks
// FactoryError TryUnlockChunk(ChunkKey chunk);
// private:
// void RefreshUnlockedChunks();
// private:
// entt::entity CreateEntity();
// FactoryError AddEntity(int x, int y, Ref<Archetype> archetype, entt::entity entityID);
// void InvalidateCachedChunk();
// public: // UPGRADING
// FactoryError TryUpgradeEntity(FactoryEntity* entity);
// FactoryError TryUpgradeEntity(entt::entity entity);
// FactoryError TryUpgradeEntity(const Vector2i& position);
// FactoryError CanUpgradeEntity(entt::entity entity) const;
// FactoryError CanUpgradeEntity(FactoryEntity* entity) const;
// FactoryError CanUpgradeEntity(const Vector2i& position);
// private:
// void UpgradeEntity(entt::entity entity);
// void UpgradeEntity(entt::entity entity, Ref<Archetype> archetype);
// void SetEntityLevel(entt::entity entity, uint8_t level);
// void SetEntityLevel(entt::entity entity, Ref<Archetype> archetype, uint8_t level);
// public: // QUERY
// void HighlightUpgradableEntities(TileMapLayer* tilemap) const;
// FactoryError FindChutePath(Vector<Vector2i>& path, Vector2i startPos, Vector2i endPos) const;
// Tile const* Raycast(Vector2i startPos, Vector2i endPos) const;
// bool IsSupport(int x, int y) const;
// bool IsSupport(entt::entity entity) const;
// public: // INVENTORY
// void SetInventory(const Vector<Ref<ItemConfig>>& items);
// Inventory& GetInventory() { return WorldInventory; }
// const Inventory& GetInventory() const { return WorldInventory; }
// Inventory GetWorldInventory(Vector2 position) const;
// Inventory GetWorldInventory(entt::entity entity) const;
// public: // DATA
// const Recipe& GetRecipe(Ref<RecipeConfig> config) { return WorldInstanceData.GetRecipe(config); }
// public: // SUPPORT
// // FactoryError CanPlaceSupport(int x, int y) const;
// // FactoryError CanRemoveSupport(int x, int y) const;
// // void RegisterSupport(int x, int y, Support& support);
// // void RemoveSupport(int x, int y);
// private:
// bool SupportCheckerHelper(entt::entity entity) const;
// uint8_t SupportValueHelper(entt::entity entity) const;
// uint8_t GetSupportValue(int x, int y) const;
// bool CheckIfSupportHelper(entt::entity entity, Support& support) const;
// void ConnectSupports(Vector2i pos, Support& support, Vector2i direction);
// private:
// Mutex WorldAccessMutex;
// // std::shared_ptr<FactoryCommandQueue> CommandQueue = std::make_shared<FactoryCommandQueue>();
// // FactoryWorldInterface* Interface;
// ChunkCollection Chunks{};
// entt::registry Registry{};
// Ref<FactoryWorldSettings> WorldSettings{};
// int32_t Seed{};
// int32_t LastDrawnFrame{};
// Inventory64 WorldInventory;
// WorldData WorldInstanceData;
// Vector<ChunkKey> UnlockedChunks{ ChunkKey{} };
// Vector<ChunkUnlock> UnlockableChunks{};
// };

View File

@@ -0,0 +1,95 @@
// #pragma once
// #include "Data/WorldSettings.h"
// #include "Chunk.h"
// class FactoryWorld;
// // class WorldGenerator final
// // {
// // public:
// // public:
// // WorldGenerator() = default;
// // WorldGenerator(Ref<FactoryWorldSettings> settings, int32_t seed);
// // public:
// // // bool GenerateChunk(ChunkKey chunkKey, Chunk& chunk) const;
// // // bool GenerateChunk(ChunkKey chunkKey, Chunk& chunk, Ref<LayerConfig> layer, Ref<LayerConfig> nextLayer = {}) const;
// // // Vector<SpawnedEntities> SpawnEntities(ChunkKey chunkKey, Chunk& chunk, const std::vector<EntityTile>& persistantEntities = {}) const;
// // // Vector<SpawnedEntities> SpawnEntities(ChunkKey chunkKey, Chunk& chunk, Ref<LayerConfig> layer, Ref<LayerConfig> nextLayer = {}, const std::vector<EntityTile>& persistantEntities = {}) const;
// // // std::unique_ptr<CreatedVisualsChunk> CreateChunkVisuals(ChunkKey chunkKey, Chunk& chunk);
// // public:
// // void ThreadedGenerateChunk(ChunkKey chunkKey, std::function<void(ChunkData&&)> callback, const std::vector<EntityTile>& persistantEntities = {});
// // private:
// // public:
// // Ref<FactoryWorldSettings> Settings{};
// // WorldGraph Graph{};
// // int32_t Seed{};
// // };
// class ChunkGenerator final
// {
// public:
// struct SpawnedEntities
// {
// Ref<Archetype> Archetype{};
// Vector2i SpawnPosition{};
// Vector<Vector2i> ClaimedPositions{};
// };
// struct CreatedVisualsTile
// {
// CreatedVisualsTile() = default;
// CreatedVisualsTile(uint16_t atlasCoordinateX, uint16_t atlasCoordinateY, uint16_t atlasIndex) : AtlasCoordinateX{ atlasCoordinateX }, AtlasCoordinateY{ atlasCoordinateY }, AtlasIndex{ atlasIndex } {};
// uint16_t AtlasCoordinateX{};
// uint16_t AtlasCoordinateY{};
// uint16_t AtlasIndex{};
// };
// typedef std::array<CreatedVisualsTile, Chunk::TotalChunkTiles> CreatedVisualsChunk;
// typedef std::array<int8_t, Chunk::TotalChunkTiles> ChunkShadowValues;
// typedef std::function<void(std::unique_ptr<Chunk>&&)> CreatedChunkCallback;
// typedef std::function<void(const Vector<SpawnedEntities>&)> SpawnedEntitiesCallback;
// typedef std::function<void(CreatedVisualsChunk*)> VisualizedChunkCallback;
// typedef std::function<void(ChunkShadowValues*)> ShadowsCallback;
// ChunkGenerator() = default;
// private:
// ChunkGenerator(Ref<FactoryWorldSettings> settings, ChunkKey chunk, int32_t seed) : Settings{ settings }, ChunkInfo{ chunk }, Seed{ seed } {};
// public:
// static void GenerateChunk(Ref<FactoryWorldSettings> settings, ChunkKey chunkInfo, int seed, CreatedChunkCallback chunkCallback, SpawnedEntitiesCallback entitiesCallback = {}, VisualizedChunkCallback visualsCallback = {}, ShadowsCallback shadowsCallback = {});
// std::unique_ptr<Chunk> GenerateChunk();
// private:
// static void GenerateChunk(void* pData);
// void GenerateChunkInternal(CreatedChunkCallback chunkCallback, SpawnedEntitiesCallback entitiesCallback, VisualizedChunkCallback visualsCallback, ShadowsCallback shadowsCallback);
// void GenerateChunkTiles() const;
// Vector<SpawnedEntities> SpawnEntities(const std::vector<EntityTile>& persistantEntities = {}) const;
// std::unique_ptr<CreatedVisualsChunk> CreateVisuals();
// std::unique_ptr<ChunkShadowValues> CascadeShadows();
// Pair<Ref<LayerConfig>, Ref<LayerConfig>> GetLayers() const;
// void FillChunkCollection(int relativeX, int relativeY, ChunkCollection& collection) const;
// void CascadeShadows_Recursive(std::array<int8_t, WorldNodeParameters::PaddedChunkSize>& values, int posX, int posY, int value);
// Tile GetTile(int x, int y) const;
// bool InBounds(int x, int y) const;
// private:
// Ref<FactoryWorldSettings> Settings{};
// ChunkKey ChunkInfo{};
// int32_t Seed{};
// std::unique_ptr<WorldNodeParameters::TileArray> TileArray{};
// };

View File

@@ -0,0 +1,29 @@
#pragma once
#include "flecs.h"
#include "Components/Configs/WorldConfig.hpp"
class WorldInstance
{
public:
WorldInstance() = default;
WorldInstance(const WorldConfig& worldConfig);
~WorldInstance() = default;
public:
void ProcessFrame();
public:
flecs::world& GetEcsWorld() { return EcsWorld; }
const flecs::world& GetEcsWorld() const { return EcsWorld; }
private:
static void RegisterTypes(flecs::world& world);
private:
flecs::world EcsWorld{};
};

155
include/Types/Archetype.h Normal file
View File

@@ -0,0 +1,155 @@
// #pragma once
// #include "core/io/resource.h"
// #include "scene/resources/packed_scene.h"
// #include "scene/resources/texture.h"
// #include "UpgradeLevel.h"
// #include "modules/factory/include/Data/Tile.h"
// #include "Core/Chunk.h"
// class SpawnDescription : public Resource
// {
// GDCLASS(SpawnDescription, Resource);
// public:
// static void _bind_methods();
// public:
// virtual bool IsValid(const ChunkCollection& chunk, Vector2i pos) = 0;
// virtual void ClaimTiles(Vector<Vector2i>& tiles, const ChunkCollection& chunk, Vector2i pos) {};
// public:
// int GetFilter() const { return Filter; }
// auto GetTile() const { return Tile; }
// void SetFilter(int filter) { Filter = static_cast<TILE_TYPE>(filter); }
// void SetTile(Ref<TileConfig> tile) { Tile = tile; }
// protected:
// bool FilterTile(Tile tile) { return Tile.is_valid() ? (Tile->TileData == tile) : (tile.GetType() == Filter); }
// bool TryFilterTile(const ChunkCollection& chunks, int x, int y);
// bool TryFilterTile(const ChunkCollection& chunks, Vector2i pos);
// protected:
// TILE_TYPE Filter{ TILE_NONE };
// Ref<TileConfig> Tile{};
// };
// class SpawnNearby : public SpawnDescription
// {
// GDCLASS(SpawnNearby, SpawnDescription);
// public:
// static void _bind_methods();
// protected:
// virtual bool IsValid(const ChunkCollection& chunk, Vector2i pos) override;
// public:
// int GetRange() const { return Range; }
// void SetRange(int range) { Range = static_cast<int8_t>(range); }
// private:
// int8_t Range{};
// };
// class OccupiedTiles : public SpawnDescription
// {
// GDCLASS(OccupiedTiles, SpawnDescription);
// public:
// static void _bind_methods();
// protected:
// virtual bool IsValid(const ChunkCollection& chunk, Vector2i pos) override;
// virtual void ClaimTiles(Vector<Vector2i>& tiles, const ChunkCollection& chunk, Vector2i pos) override;
// public:
// TypedArray<Vector2i> GetOffset() const { return VectorToTypedArrayVariant(Offsets); }
// void SetOffset(TypedArray<Vector2i> offsets) { Offsets = TypedArrayToVectorVariant(offsets); }
// public:
// Vector<Vector2i> Offsets;
// };
// class RequiredTiles : public SpawnDescription
// {
// GDCLASS(RequiredTiles, SpawnDescription);
// public:
// static void _bind_methods();
// protected:
// virtual bool IsValid(const ChunkCollection& chunk, Vector2i pos) override;
// public:
// TypedArray<Vector2i> GetOffset() const { return VectorToTypedArrayVariant(Offsets); }
// void SetOffset(TypedArray<Vector2i> offsets) { Offsets = TypedArrayToVectorVariant(offsets); }
// public:
// Vector<Vector2i> Offsets;
// };
// class LinkedTiles : public SpawnDescription
// {
// GDCLASS(LinkedTiles, SpawnDescription);
// public:
// static void _bind_methods();
// protected:
// virtual bool IsValid(const ChunkCollection& chunk, Vector2i pos) override;
// virtual void ClaimTiles(Vector<Vector2i>& tiles, const ChunkCollection& chunk, Vector2i pos) override;
// public:
// void ClaimTiles_Recursive(Vector<Vector2i>& tiles, const ChunkCollection& chunk, Vector2i pos);
// };
// class Archetype : public Resource
// {
// GDCLASS(Archetype, Resource);
// public:
// static void _bind_methods();
// public:
// Ref<PackedScene> GetScene() const { return Scene; }
// Ref<PackedScene> GetPreviewScene() const { return PreviewScene; }
// Ref<Texture2D> GetPreviewTexture() const { return PreviewTexture; }
// TypedArray<SpawnDescription> GetOccupiedTiles() const { return VectorToTypedArray(SpawnConditions); }
// TypedArray<UpgradeLevelConfig> GetUpgrades() const { return VectorToTypedArray(Upgrades); }
// int GetID() const { return ID; }
// void SetScene(Ref<PackedScene> scene) { Scene = scene; }
// void SetPreviewScene(Ref<PackedScene> scene) { PreviewScene = scene; }
// void SetPreviewTetxture(Ref<Texture2D> preview) { PreviewTexture = preview; }
// void SetOccupiedTiles(TypedArray<SpawnDescription> tiles) { SpawnConditions = TypedArrayToVector(tiles); }
// void SetUpgrades(TypedArray<UpgradeLevelConfig> upgrades) { Upgrades = TypedArrayToVector(upgrades); }
// void SetID(int id) { ID = id; }
// public:
// Ref<PackedScene> Scene{};
// Ref<PackedScene> PreviewScene{};
// Ref<Texture2D> PreviewTexture{};
// Vector<Ref<UpgradeLevelConfig>> Upgrades{};
// Vector<Ref<SpawnDescription>> SpawnConditions{};
// int ID{ -1 };
// };
// class PlaceableArchetype : public Resource
// {
// GDCLASS(PlaceableArchetype, Resource);
// public:
// static void _bind_methods();
// public:
// auto GetArchetype() const { return Archetype; }
// auto GetPlaceCosts() const { return VectorToTypedArray(PlaceCosts); }
// void SetArchetype(Ref<Archetype> archetype) { Archetype = archetype; }
// void SetPlaceCosts(TypedArray<ItemAmountConfig> costs) { PlaceCosts = TypedArrayToVector(costs); }
// public:
// Ref<Archetype> Archetype{};
// Vector<Ref<ItemAmountConfig>> PlaceCosts{};
// };

20
include/Types/Filter.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include "Item.hpp"
// struct alignas(4) ItemFilter final
// {
// uint16_t FilterItem0;
// uint16_t FilterItem1;
// uint16_t FilterItem2;
// //Item::ItemFlags FilterFlags;
// public:
// inline bool ApplyFilter(Item item)
// {
// return //(item.Flags & FilterFlags) &&
// (FilterItem0 == 0 || FilterItem0 == item.ItemID) &&
// (FilterItem1 == 0 || FilterItem1 == item.ItemID) &&
// (FilterItem2 == 0 || FilterItem2 == item.ItemID);
// }
// };

48
include/Types/Grid8x8.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <stdint.h>
// 8x8 bitset grid. Each bit represents a cell: bit index = (y << 3) | x.
struct Grid8x8 final
{
struct Cell
{
int X, Y;
};
class Iterator
{
uint64_t Remaining;
public:
explicit Iterator(uint64_t bits) : Remaining(bits) {}
bool operator!=(const Iterator& other) const { return Remaining != other.Remaining; }
Cell operator*() const
{
int pos = __builtin_ctzll(Remaining);
return { pos & 7, pos >> 3 };
}
Iterator& operator++()
{
Remaining &= Remaining - 1; // clear lowest set bit
return *this;
}
};
Iterator begin() const { return Iterator(Bits); }
Iterator end() const { return Iterator(0); }
// Number of filled cells.
int Count() const { return __builtin_popcountll(Bits); }
bool Get(int x, int y) const { return (Bits >> ((y << 3) | x)) & 1; }
void Set(int x, int y) { Bits |= uint64_t(1) << ((y << 3) | x); }
void Clear(int x, int y) { Bits &= ~(uint64_t(1) << ((y << 3) | x)); }
uint64_t Bits = 0;
};
static_assert(sizeof(Grid8x8) == 8);

52
include/Types/Item.hpp Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <stdint.h>
#include <limits>
#include "flecs.h"
#include "config.h"
struct Item final
{
public:
static constexpr UnderlyingItemT null = std::numeric_limits<UnderlyingItemT>::max();
public:
UnderlyingItemT ItemID{ null };
public:
Item() = default;
Item(int id)
{
DEV_ASSERT(id != null);
ItemID = static_cast<UnderlyingItemT>(id);
}
};
template <typename IntegralType>
struct ItemAmountT final
{
public:
ItemAmountT(Item item, IntegralType amount) : ItemId{ item }, Amount{ amount } {}
ItemAmountT() = default;
public:
Item ItemId;
IntegralType Amount;
};
typedef ItemAmountT<uint16_t> ItemAmount16;
typedef ItemAmountT<uint32_t> ItemAmount32;
typedef ItemAmountT<uint64_t> ItemAmount64;
typedef ItemAmount16 ItemAmount;
inline void Flecs_Item(flecs::world& world)
{
world.component<Item>()
.member<uint16_t>("ItemID");
world.component<ItemAmount32>()
.member<Item>("ItemRef")
.member<uint32_t>("Amount");
}

View File

@@ -0,0 +1,152 @@
#pragma once
// #include "Tile.h"
// #include "core/object/ref_counted.h"
// #include "modules/noise/fastnoise_lite.h"
// #include "core/io/resource.h"
// #include "modules/factory/include/Data/Archetype.h"
// #include "modules/factory/include/Util/Helpers.h"
// #include "modules/factory/include/Data/WorldGraph/WorldGraphVisualNode.h"
// #include "scene/resources/image_texture.h"
// struct LayerTileConfig final : public Resource
// {
// GDCLASS(LayerTileConfig, Resource);
// public:
// static void _bind_methods();
// public:
// Ref<WorldGraphVisualNodeBase> GetTileGenerator() const { return TileGenerator; }
// Ref<TileConfig> GetTile() const { return Tile; }
// Color GetPreviewColor() const { return PreviewColor; }
// void SetTileGenerator(Ref<WorldGraphVisualNodeBase> gen) { TileGenerator = gen; }
// void SetTile(Ref<TileConfig> tile) { Tile = tile; }
// void SetPreviewColor(Color color) { PreviewColor = color; }
// public:
// Ref<WorldGraphVisualNodeBase> TileGenerator{};
// Ref<TileConfig> Tile{};
// Color PreviewColor{};
// };
// struct LayerConfig final : public Resource
// {
// GDCLASS(LayerConfig, Resource);
// public:
// static void _bind_methods();
// public:
// int GetStartChunk() const { return StartChunk; }
// auto GetTiles() const { return VectorToTypedArray(Tiles); }
// auto GetRegisteredNodes() const { return VectorToTypedArray(AllNodes); }
// auto GetArchetypes() const { return VectorToTypedArray(SpawningArchetypes); }
// void SetStartChunk(int height) { StartChunk = height; }
// void SetTiles(TypedArray<LayerTileConfig> tiles);
// void SetRegisteredNodes(TypedArray<WorldGraphVisualNodeBase> allNodes) { AllNodes = TypedArrayToVector(allNodes); }
// void SetArchetypes(TypedArray<Archetype> archetypes) { SpawningArchetypes = TypedArrayToVector(archetypes); }
// void RegisterVisualNode(Ref<WorldGraphVisualNodeBase> node) { if (!HasVisualNode(node)) AllNodes.push_back(node); }
// void RemoveVisualNode(Ref<WorldGraphVisualNodeBase> node) { AllNodes.erase(node);}
// bool HasVisualNode(Ref<WorldGraphVisualNodeBase> node) const { return AllNodes.has(node); }
// bool IsValid() const;
// bool CanConnect(Ref<WorldGraphVisualNodeBase> from, Ref<WorldGraphVisualNodeBase> to) const;
// Ref<ImageTexture> CreateTexture(Vector2i chunk, int seed) const;
// public:
// int StartChunk{};
// Vector<Ref<LayerTileConfig>> Tiles{};
// Vector<Ref<WorldGraphVisualNodeBase>> AllNodes{};
// Vector<Ref<Archetype>> SpawningArchetypes{};
// Vector<Ref<ItemConfig>> UnlockedItems{};
// Vector<Ref<Archetype>> UnlockedBuildings{};
// Vector<Ref<RecipeConfig>> UnlockedRecipes{};
// };
// struct LayerConfig_Filler final : public Resource
// {
// GDCLASS(LayerConfig_Filler, Resource);
// public:
// static void _bind_methods();
// public:
// float GetPassagewayWidthRatio() const { return PassagewayWidthRatio; }
// Ref<FastNoiseLite> GetNoise() const { return NoiseGenerator; }
// Ref<TileConfig> GetTile() const { return Tile; }
// void SetPassagewayWidthRatio(float ratio) { PassagewayWidthRatio = ratio; }
// void SetNoise(Ref<FastNoiseLite> noise) { NoiseGenerator = noise; }
// void SetTile(Ref<TileConfig> tile) { Tile = tile; }
// public:
// float PassagewayWidthRatio = 0.05f;
// Ref<FastNoiseLite> NoiseGenerator{};
// Ref<TileConfig> Tile{};
// };
// struct LayerConfig_Ore final : public Resource
// {
// GDCLASS(LayerConfig_Ore, Resource);
// public:
// static void _bind_methods();
// public:
// float GetRatio() const { return Ratio; }
// Ref<Archetype> GetOre() { return OreArchetype; }
// Ref<FastNoiseLite> GetNoise() const { return NoiseGenerator; }
// void SetRatio(float ratio) { Ratio = ratio; }
// void SetOre(Ref<Archetype> archetype) { OreArchetype = archetype; }
// void SetNoise(Ref<FastNoiseLite> noise) { NoiseGenerator = noise; }
// public:
// float Ratio = 0.05f;
// Ref<Archetype> OreArchetype;
// Ref<FastNoiseLite> NoiseGenerator{};
// };
// struct LayerConfig_Plant final : public Resource
// {
// GDCLASS(LayerConfig_Plant, Resource);
// public:
// static void _bind_methods();
// public:
// auto GetGrowTile() const { return GrowTileID; }
// float GetGrowChance() const { return GrowChance; }
// Ref<Archetype> GetPlantArchetype() const { return PlantArchetype; }
// void SetGrowTile(const Ref<TileConfig>& growTile) { GrowTileID = growTile; }
// void SetGrowChance(float chance) { GrowChance = chance; }
// void SetPlantArchetype(const Ref<Archetype>& archetype) { PlantArchetype = archetype; }
// public:
// Ref<TileConfig> GrowTileID{}; // Tile it can grow on
// float GrowChance{}; // Value from 0 to 1
// Ref<Archetype> PlantArchetype{};
// };
// struct LayerConfig_Liquid final : public Resource
// {
// GDCLASS(LayerConfig_Liquid, Resource);
// public:
// static void _bind_methods() {};
// public:
// };
// struct LayerConfig_NPC final : public Resource
// {
// GDCLASS(LayerConfig_NPC, Resource);
// public:
// static void _bind_methods() {};
// };
// template <typename T>
// struct CompareLayerHeights {
// bool operator()(const Ref<T> p_a, const Ref<T> p_b) const { return p_b->MaxHeight < p_a->MaxHeight; }
// };

46
include/Types/Recipe.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include "Item.hpp"
#include "Util/SharedBuffer.h"
#include "Util/Span.h"
struct RecipeConfig;
struct Recipe final
{
struct RecipeMeta final
{
uint8_t IngredientsAmount;
uint8_t ResultsAmount;
uint16_t ProcessingTime;
uint32_t RecipeID{};
};
public:
Recipe() = default;
Recipe(const RecipeConfig& recipe);
public:
tcb::span<const ItemAmount> GetIngredients() const { assert(Data); return tcb::span<const ItemAmount>(Data.Ptr(), Data.GetMetaData()->IngredientsAmount); };
tcb::span<const ItemAmount> GetResults() const { assert(Data); return tcb::span<const ItemAmount>(Data.Ptr() + Data.GetMetaData()->IngredientsAmount, Data.GetMetaData()->ResultsAmount); };
uint32_t GetRecipeID() const { return Data.GetMetaData()->RecipeID; }
uint16_t GetProcessingTime() const { return Data.GetMetaData()->ProcessingTime; }
public:
operator bool() const { return Data; }
public:
SharedBuffer<ItemAmount, RecipeMeta> Data;
};

74
include/Types/Tile.h Normal file
View File

@@ -0,0 +1,74 @@
#pragma once
#include <stdint.h>
enum class TileType : uint8_t
{
Air,
Filler,
Liquid,
Ore,
NPC,
Plant,
MAX,
NONE = 0b111,
};
static_assert(static_cast<uint8_t>(TileType::MAX) <= static_cast<uint8_t>(TileType::NONE));
struct Tile final
{
typedef uint16_t TileDataType;
static constexpr int BytesID = 12;
static constexpr int BytesType = 3;
static constexpr int BytesEntity = 1;
static constexpr TileDataType MaskID = 0b0000111111111111;
static constexpr TileDataType MaskType = 0b0111000000000000;
static constexpr TileDataType MaskEntity = 0b1000000000000000;
static constexpr TileDataType InvalidID = MaskID;
public:
Tile() = default;
explicit Tile(TileDataType id)
{
Data = id;
}
public:
void FlagAsEntity() { Data |= MaskEntity; }
void RemoveEntityFlag() { Data &= ~MaskEntity; }
constexpr bool HasEntity() const { return Data & MaskEntity; }
constexpr uint16_t GetID() const { return Data & MaskID; }
constexpr bool IsAir() const { return GetType() == TileType::Air; }
constexpr bool IsFiller() const { return GetType() == TileType::Filler; }
constexpr bool IsLiquid() const { return GetType() == TileType::Liquid; }
constexpr bool IsOre() const { return GetType() == TileType::Ore; }
constexpr bool IsPlant() const { return GetType() == TileType::Plant; }
constexpr bool IsNPC() const { return GetType() == TileType::NPC; }
constexpr TileType GetType() const { return static_cast<TileType>((Data & MaskType) >> BytesID); }
constexpr bool IsType(TileType type) const { return GetType() == type; }
void SetID(uint16_t id) { Data = (Data & ~MaskID) | (id & MaskID); }
void SetAir() { SetType(TileType::Air); }
void SetFiller() { SetType(TileType::Filler); }
void SetLiquid() { SetType(TileType::Liquid); }
void SetOre() { SetType(TileType::Ore); }
void SetPlant() { SetType(TileType::Plant); }
void SetNPC() { SetType(TileType::NPC); }
void SetType(TileType type) { Data = (Data & ~MaskType) | (static_cast<uint16_t>(type) << BytesID); }
constexpr uint16_t AsInt() const { return Data; }
constexpr bool IsValid() const { return Data == MaskID; }
private:
TileDataType Data{ MaskID };
};
inline constexpr bool operator==(Tile lhs, Tile rhs) { return lhs.AsInt() == rhs.AsInt(); }
inline constexpr bool operator!=(Tile lhs, Tile rhs) { return lhs.AsInt() != rhs.AsInt(); }
static_assert(sizeof(Tile) == 2);

View File

@@ -0,0 +1,25 @@
// #pragma once
// #include "core/io/resource.h"
// #include "core/templates/vector.h"
// #include "core/variant/dictionary.h"
// #include "modules/factory/include/Util/Helpers.h"
// #include "Item.h"
// class UpgradeLevelConfig : public Resource
// {
// GDCLASS(UpgradeLevelConfig, Resource);
// public:
// static void _bind_methods();
// public:
// TypedArray<ItemAmountConfig> GetUpgradeCosts() const { return VectorToTypedArray(UpgradeCost); }
// Dictionary GetUpgradeResults() const { return UpgradeResults; }
// void SetUpgradeCosts(TypedArray<ItemAmountConfig> upgradeCosts) { UpgradeCost = TypedArrayToVector<ItemAmountConfig>(upgradeCosts); }
// void SetUpgradeResults(Dictionary results) { UpgradeResults = results; }
// public:
// Vector<Ref<ItemAmountConfig>> UpgradeCost{};
// Dictionary UpgradeResults{};
// };

View File

@@ -0,0 +1,30 @@
#pragma once
#include "WorldGraphVisualNode.h"
class WorldGraph final
{
public:
WorldGraph() = default;
WorldGraph(const Vector<Ref<WorldGraphVisualNodeBase>>& nodes);
WorldGraph(const WorldGraph& other);
WorldGraph(WorldGraph&& other) noexcept = default;
WorldGraph& operator=(const WorldGraph& other);
WorldGraph& operator=(WorldGraph&& other) noexcept = default;
public:
Variant Execute(Ref<WorldGraphVisualNodeBase> node, const WorldNodeParameters& params) const;
WorldNodeBase* GetNode(Ref<WorldGraphVisualNodeBase> node) const;
private:
void Compile(const Vector<Ref<WorldGraphVisualNodeBase>>& nodes);
std::unique_ptr<WorldNodeBase*[]> CopyMemory(HashMap<Ref<WorldGraphVisualNodeBase>, WorldNodeBase*>& nodeMap) const;
private:
int MemorySize{};
std::unique_ptr<WorldNodeBase*[]> CompiledData{};
HashMap<Ref<WorldGraphVisualNodeBase>, WorldNodeBase*> NodeMap{};
};

View File

@@ -0,0 +1,70 @@
#pragma once
#include <cstdint>
#include <memory>
#include "modules/factory/include/Util/Span.h"
struct WorldGraphAllocatorBase
{
virtual ~WorldGraphAllocatorBase() = default;
virtual void* Allocate(tcb::span<const uint8_t> data) = 0;
virtual void Clear() = 0;
template <typename T>
T* Allocate(const T& val)
{
return static_cast<T*>(Allocate(tcb::span<const uint8_t>(reinterpret_cast<uint8_t const*>(&val), sizeof(T))));
}
};
struct WorldGraphAllocator : public WorldGraphAllocatorBase
{
virtual ~WorldGraphAllocator() = default;
WorldGraphAllocator() = default;
WorldGraphAllocator(uint32_t totalSize) : Data {std::make_unique<uint8_t[]>(totalSize)}, Size{totalSize} {}
virtual void* Allocate(tcb::span<const uint8_t> data) override
{
auto size = (data.size_bytes() + sizeof(void*) - 1) / sizeof(void*) * sizeof(void*); // make sure aligment is 8/4 bytes for pointers
if (CurrentOffset + size > Size)
throw std::exception{};
std::memcpy(Data.get() + CurrentOffset, data.data(), data.size_bytes());
CurrentOffset += size;
return Data.get() + CurrentOffset - size;
}
virtual void Clear() override
{
CurrentOffset = 0;
}
void* GetCurrentAddress() const
{
return Data.get() + CurrentOffset;
}
std::unique_ptr<uint8_t[]> Data{};
uint32_t Size{};
uint32_t CurrentOffset{};
};
struct WorldGraphSizeMeasurer : public WorldGraphAllocatorBase
{
virtual ~WorldGraphSizeMeasurer() = default;
WorldGraphSizeMeasurer() = default;
virtual void* Allocate(tcb::span<const uint8_t> data) override
{
TotalSize += (data.size_bytes() + sizeof(void*) - 1) / sizeof(void*) * sizeof(void*);
return nullptr;
}
virtual void Clear() override
{
TotalSize = 0;
}
uint32_t TotalSize{};
};

View File

@@ -0,0 +1,665 @@
#pragma once
#include <tuple>
#include <type_traits>
#include <utility>
#include <functional>
#include <array>
#include "WorldGraphAllocator.h"
#include "Util/FastNoiseLite.h"
#include "core/variant/variant.h"
#include "modules/factory/include/Core/Chunk.h"
// // last bit determines if it is a boolean or a float
// // if it is a float, last bit of precision will be lost (not that bad)
// // if it is a bool, the bool value will be stored on the second bit
// // for floats the last bit is set
// // for bools the last bit is unset
// class BoolFloat
// {
// public:
// BoolFloat() = default;
// BoolFloat(bool val) {}
// BoolFloat(float val) {}
// public:
// void SetFloat(float val) { Data = reinterpret_cast<uint32_t&>(val) | 0b1u; }
// void SetBool(bool val) { Data = (val << 1) & (~0b1u); }
// constexpr float IsFloat() const { return Data & 0b1u; }
// constexpr float IsBool() const { return Data & (~0b1u); }
// float GetFloat() const { _ASSERT(IsFloat()); return reinterpret_cast<const float&>(Data); }
// float GetBool() const { _ASSERT(IsBool()); return Data; }
// public:
// operator float() const { return GetFloat(); }
// private:
// uint32_t Data{};
// };
struct WorldNodeParameters;
struct alignas(void*) WorldNodeBase
{
virtual Variant Evaluate(const WorldNodeParameters& params) const = 0;
virtual Variant::Type GetReturnType() const = 0;
virtual Vector<Variant::Type> GetInputTypes() const = 0;
virtual bool IsValid() const = 0;
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const = 0;
virtual void SetInput(int index, WorldNodeBase* input) = 0;
};
struct WorldNodeParameters
{
static constexpr int MaxQueryOffset = 4;
static constexpr int PaddedChunkSide = Chunk::ChunkSize + MaxQueryOffset * 2;
static constexpr int PaddedChunkSize = PaddedChunkSide * PaddedChunkSide;
typedef std::array<Tile, PaddedChunkSize> TileArray;
int X{ };
int Y{ };
int Seed{ };
float FinalValueSubstract{ };
ChunkKey ChunkInfo{ };
TileArray* GeneratedTiles{ };
Tile GetTile(int x, int y) const
{
auto bounds = GetGenerationBounds();
if (unlikely(!bounds.has_point(Vector2i{x, y})))
return {};
// DEV_ASSERT(bounds.has_point(Vector2i{x, y}));
return (*GeneratedTiles)[(y - bounds.position.y) * PaddedChunkSide + (x - bounds.position.x)];
}
static int GetArrayIndex(int x, int y)
{
return (y + MaxQueryOffset) * PaddedChunkSide + (x + MaxQueryOffset);
}
Rect2i GetGenerationBounds() const
{
return ChunkInfo.GetBounds().grow(MaxQueryOffset);
}
};
template <typename T>
struct TtoVariant
{
typedef Variant TVariant;
};
template <typename Return, typename ... Inputs>
struct WorldNodeTemplated : public WorldNodeBase
{
std::array<WorldNodeBase*, sizeof...(Inputs)> InputNodes{};
virtual Variant::Type GetReturnType() const override { return Variant::get_type_t<Return>(); }
virtual Vector<Variant::Type> GetInputTypes() const override
{
return { Variant::get_type_t<Inputs>()... };
};
virtual bool IsValid() const override
{
bool valid{ true };
for (auto input : InputNodes)
{
valid = valid && input;
}
return valid;
}
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override { Allocator->Allocate(*this); }
virtual Variant Evaluate(const WorldNodeParameters& params) const override
{
std::array<Variant, sizeof...(Inputs)> results{};
for (int i{}; i < sizeof...(Inputs); ++i)
{
results[i] = InputNodes[i]->Evaluate(params);
}
auto EvaluateFunctor = [this](typename TtoVariant<Inputs>::TVariant... args) -> Variant
{
return EvaluateT(args.get_unsafe_t<Inputs>()...);
};
return std::apply(EvaluateFunctor, results);
}
virtual void SetInput(int index, WorldNodeBase* input) override
{
InputNodes[index] = input;
}
virtual Return EvaluateT(Inputs...) const = 0;
};
struct WorldNode_Add : public WorldNodeTemplated<float, float, float>
{
virtual float EvaluateT(float v0, float v1) const override
{
return v0 + v1;
}
};
struct WorldNode_Subtract : public WorldNodeTemplated<float, float, float>
{
virtual float EvaluateT(float v0, float v1) const override
{
return v0 - v1;
}
};
struct WorldNode_Multiply : public WorldNodeTemplated<float, float, float>
{
virtual float EvaluateT(float v0, float v1) const override
{
return v0 * v1;
}
};
struct WorldNode_Divide : public WorldNodeTemplated<float, float, float>
{
virtual float EvaluateT(float v0, float v1) const override
{
return v0 / v1;
}
};
struct WorldNode_Modulo : public WorldNodeTemplated<float, float, float>
{
virtual float EvaluateT(float v0, float v1) const override
{
return fmod(v0, v1);
}
};
struct WorldNode_Equal : public WorldNodeTemplated<bool, float, float>
{
virtual bool EvaluateT(float v0, float v1) const override
{
return v0 == v1;
}
};
struct WorldNode_Smaller : public WorldNodeTemplated<bool, float, float>
{
virtual bool EvaluateT(float v0, float v1) const override
{
return v0 < v1;
}
};
struct WorldNode_Greater : public WorldNodeTemplated<bool, float, float>
{
virtual bool EvaluateT(float v0, float v1) const override
{
return v0 > v1;
}
};
struct WorldNode_SmallerEqual : public WorldNodeTemplated<bool, float, float>
{
virtual bool EvaluateT(float v0, float v1) const override
{
return v0 <= v1;
}
};
struct WorldNode_GreaterEqual : public WorldNodeTemplated<bool, float, float>
{
virtual bool EvaluateT(float v0, float v1) const override
{
return v0 >= v1;
}
};
struct WorldNode_Negate : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return -v;
}
};
struct WorldNode_Abs : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return std::abs(v);
}
};
struct WorldNode_Ceil : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return std::ceil(v);
}
};
struct WorldNode_Floor : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return std::floor(v);
}
};
struct WorldNode_Sin : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return std::sin(v);
}
};
struct WorldNode_Cos : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return std::cos(v);
}
};
struct WorldNode_Tan : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return std::tan(v);
}
};
struct WorldNode_Exp : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return std::exp(v);
}
};
struct WorldNode_Pow : public WorldNodeTemplated<float, float, float>
{
virtual float EvaluateT(float v0, float v1) const override
{
return std::pow(v0, v1);
}
};
struct WorldNode_Max : public WorldNodeTemplated<float, float, float>
{
virtual float EvaluateT(float v0, float v1) const override
{
return std::max(v0, v1);
}
};
struct WorldNode_Min : public WorldNodeTemplated<float, float, float>
{
virtual float EvaluateT(float v0, float v1) const override
{
return std::min(v0, v1);
}
};
struct WorldNode_Clamp : public WorldNodeTemplated<float, float, float, float>
{
virtual float EvaluateT(float v0, float v1, float v2) const override
{
return std::clamp(v0, v1, v2);
}
};
struct WorldNode_Round : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return std::round(v);
}
};
struct WorldNode_Log : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return std::log(v);
}
};
struct WorldNode_Square : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float v) const override
{
return v * v;
}
};
struct WorldNode_Lerp : public WorldNodeTemplated<float, float, float, float>
{
virtual float EvaluateT(float from, float to, float weight) const override
{
return Math::lerp(from, to, weight);
}
};
struct WorldNode_OneMinus : public WorldNodeTemplated<float, float>
{
virtual float EvaluateT(float val) const override
{
return 1 - val;
}
};
struct WorldNode_And : public WorldNodeTemplated<bool, bool, bool>
{
virtual bool EvaluateT(bool val0, bool val1) const override
{
return val0 && val1;
}
};
struct WorldNode_Or : public WorldNodeTemplated<bool, bool, bool>
{
virtual bool EvaluateT(bool val0, bool val1) const override
{
return val0 || val1;
}
};
struct WorldNode_Constant : public WorldNodeBase
{
float Value{};
virtual Variant Evaluate(const WorldNodeParameters& params) const override
{
return Value;
}
virtual Variant::Type GetReturnType() const override { return Variant::FLOAT; }
virtual Vector<Variant::Type> GetInputTypes() const override
{
return { };
};
virtual bool IsValid() const override
{
return !std::_Is_nan(Value);
}
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override
{
Allocator->Allocate(*this);
}
virtual void SetInput(int index, WorldNodeBase* input) override
{
DEV_ASSERT(false);
}
};
struct WorldNode_Branch : public WorldNodeBase
{
WorldNodeBase* InputBool{};
WorldNodeBase* InputTrue{};
WorldNodeBase* InputFalse{};
virtual Variant Evaluate(const WorldNodeParameters& params) const override
{
bool condition = InputBool->Evaluate(params).get_unsafe_bool();
return condition ? InputTrue->Evaluate(params) : InputFalse->Evaluate(params);
}
virtual Variant::Type GetReturnType() const override { return Variant::Type::FLOAT; }
virtual Vector<Variant::Type> GetInputTypes() const override
{
return { Variant::Type::BOOL, Variant::Type::FLOAT, Variant::Type::FLOAT };
};
virtual bool IsValid() const override
{
return InputBool && InputTrue && InputFalse;
}
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override
{
Allocator->Allocate(*this);
}
virtual void SetInput(int index, WorldNodeBase* input) override
{
switch (index)
{
case 0: InputBool = input;
case 1: InputTrue = input;
case 2: InputFalse = input;
}
}
};
struct WorldNode_NoiseBase : public WorldNodeBase
{
float Frequency{ 1.f };
virtual Variant Evaluate(const WorldNodeParameters& params) const override
{
float x = params.X * Frequency;
float y = params.Y * Frequency;
return EvaluateNoise(params.Seed, x, y);
}
virtual Variant::Type GetReturnType() const override { return Variant::FLOAT; }
virtual Vector<Variant::Type> GetInputTypes() const override
{
return { };
};
virtual bool IsValid() const override
{
return Frequency != 0;
}
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override
{
Allocator->Allocate(*this);
}
virtual void SetInput(int index, WorldNodeBase* input) override
{
DEV_ASSERT(false);
}
virtual float EvaluateNoise(int seed, float x, float y) const = 0;
};
struct WorldNode_Simplex : public WorldNode_NoiseBase
{
virtual float EvaluateNoise(int seed, float x, float y) const override
{
const float SQRT3 = (float)1.7320508075688772935274463415059;
const float F2 = 0.5f * (SQRT3 - 1);
float t = (x + y) * F2;
x += t;
y += t;
return fastnoiselitestatic::SingleSimplex<float>(seed, x, y);
}
};
struct WorldNode_OpenSimplex : public WorldNode_NoiseBase
{
virtual float EvaluateNoise(int seed, float x, float y) const override
{
const float SQRT3 = (float)1.7320508075688772935274463415059;
const float F2 = 0.5f * (SQRT3 - 1);
float t = (x + y) * F2;
x += t;
y += t;
return fastnoiselitestatic::SingleOpenSimplex2S<float>(seed, x, y);
}
};
struct WorldNode_Perlin : public WorldNode_NoiseBase
{
virtual float EvaluateNoise(int seed, float x, float y) const override
{
return fastnoiselitestatic::SinglePerlin<float>(seed, x, y);
}
};
struct WorldNode_ValueCubic : public WorldNode_NoiseBase
{
virtual float EvaluateNoise(int seed, float x, float y) const override
{
return fastnoiselitestatic::SingleValueCubic<float>(seed, x, y);
}
};
struct WorldNode_Value : public WorldNode_NoiseBase
{
virtual float EvaluateNoise(int seed, float x, float y) const override
{
return fastnoiselitestatic::SingleValue<float>(seed, x, y);
}
};
struct WorldNode_IsTile : public WorldNodeBase
{
int8_t RelativeX{};
int8_t RelativeY{};
TILE_TYPE TileType{};
virtual Variant Evaluate(const WorldNodeParameters& params) const override
{
return params.GetTile(params.X + RelativeX, params.Y + RelativeY).GetType() == TileType;
}
virtual Variant::Type GetReturnType() const override { return Variant::BOOL; }
virtual Vector<Variant::Type> GetInputTypes() const override
{
return { };
};
virtual bool IsValid() const override
{
return true;
}
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override
{
Allocator->Allocate(*this);
}
virtual void SetInput(int index, WorldNodeBase* input) override
{
DEV_ASSERT(false);
}
};
struct WorldNode_TileDistance : public WorldNodeBase
{
int8_t Range{};
TILE_TYPE TileType{};
private:
struct SmallVectorI2{
int8_t X; int8_t Y;
SmallVectorI2(int8_t x, int8_t y) : X{ x }, Y{ y } {};
SmallVectorI2() = default;
};
static constexpr int ArraySize{(WorldNodeParameters::MaxQueryOffset * 2 + 1) * (WorldNodeParameters::MaxQueryOffset * 2 + 1) - 1};
static std::array<SmallVectorI2, ArraySize> CreateSortedOffsets()
{
std::array<SmallVectorI2, ArraySize> offsets{};
int counter{};
for (int y{-WorldNodeParameters::MaxQueryOffset}; y <= WorldNodeParameters::MaxQueryOffset; ++y)
for (int x{-WorldNodeParameters::MaxQueryOffset}; x <= WorldNodeParameters::MaxQueryOffset; ++x)
if (y != 0 && x != 0)
{
offsets[counter] = SmallVectorI2{ static_cast<int8_t>(x), static_cast<int8_t>(y) };
++counter;
}
std::sort(offsets.begin(), offsets.end(), [] (SmallVectorI2 lhs, SmallVectorI2 rhs)
{
return rhs.X * rhs.X + rhs.Y * rhs.Y > lhs.X * lhs.X + lhs.Y * lhs.Y;
});
return offsets;
}
inline static const std::array<SmallVectorI2, ArraySize> SortedOffsets{ CreateSortedOffsets() };
static std::array<int, WorldNodeParameters::MaxQueryOffset> CreateRangeOffsets()
{
std::array<int, WorldNodeParameters::MaxQueryOffset> offsets{};
for (int i{}; i < WorldNodeParameters::MaxQueryOffset; ++i)
{
offsets[i] = ArraySize - (((WorldNodeParameters::MaxQueryOffset - i) * 2 + 1) * ((WorldNodeParameters::MaxQueryOffset - i) * 2 + 1) - 1);
}
return offsets;
}
inline static const std::array<int, WorldNodeParameters::MaxQueryOffset> RangeOffsets{ CreateRangeOffsets() };
// inline static const SmallVectorI2 SortedOffset[] =
// {
// {+3, +3}, {-3, -3}, {+3, -3}, {-3, +3}, // 3
// {+2, +3}, {+3, +2}, {-2, -3}, {-3, -2}, {-2, +3}, {-3, +2}, {+2, -3}, {+3, -2},
// {+1, +3}, {+3, +1}, {-1, -3}, {-3, -1}, {-1, +3}, {-3, +1}, {+1, -3}, {+3, -1},
// {+3, +0}, {-3, -0}, {+0, -3}, {-0, +3},
// {+2, +2}, {-2, -2}, {+2, -2}, {-2, +2}, // 2
// {+1, +2}, {+2, +1}, {-1, -2}, {-2, -1}, {-1, +2}, {-2, +1}, {+1, -2}, {+2, -1},
// {+2, +0}, {-2, -0}, {+0, -2}, {-0, +2},
// {+1, +1}, {-1, -1}, {+1, -1}, {-1, +1}, // 1
// {+1, +0}, {-1, -0}, {+0, -1}, {-0, +1},
// };
// inline static const int8_t RangeOffsets[] =
// {
// 0, 24, 40
// };
public:
virtual Variant Evaluate(const WorldNodeParameters& params) const override
{
if (!params.ChunkInfo.GetBounds().has_point(Vector2i{params.X, params.Y})) return 16'384.f;
int maxRangeSQ = 16'384;
for (int i{RangeOffsets[Range]}; i < 48; ++i)
{
auto offset = SortedOffsets[i];
if (params.GetTile(params.X + offset.X, params.Y + offset.Y).GetType() == TileType)
{
maxRangeSQ = offset.X * offset.X + offset.Y * offset.Y;
}
}
return sqrtf(static_cast<float>(maxRangeSQ));
}
virtual Variant::Type GetReturnType() const override { return Variant::FLOAT; }
virtual Vector<Variant::Type> GetInputTypes() const override
{
return { };
};
virtual bool IsValid() const override
{
return Range >= 1 && Range <= 3;
}
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override
{
Allocator->Allocate(*this);
}
virtual void SetInput(int index, WorldNodeBase* input) override
{
DEV_ASSERT(false);
}
};
// struct WorldNode_Cellular : public WorldNode_NoiseBase
// {
// virtual float EvaluateNoise(int seed, float x, float y) const override
// {
// const float SQRT3 = (float)1.7320508075688772935274463415059;
// const float F2 = 0.5f * (SQRT3 - 1);
// float t = (x + y) * F2;
// x += t;
// y += t;
// return fastnoiselite::FastNoiseLite::SingleCellular<float>(seed, x, y);
// }
// };

View File

@@ -0,0 +1,174 @@
#pragma once
#include "WorldGraphNode.h"
#include "core/io/resource.h"
#include "modules/factory/include/Util/Helpers.h"
class WorldGraphVisualNodeBase : public Resource
{
GDCLASS(WorldGraphVisualNodeBase, Resource);
public:
static void _bind_methods();
public:
virtual ~WorldGraphVisualNodeBase() = default;
public:
Vector2i GetPosition() const { return Position; }
TypedArray<WorldGraphVisualNodeBase> GetInputs() const
{
return VectorToTypedArray(InputNodes);
}
void SetPosition(Vector2i pos) { Position = pos; }
void SetInputNodes(TypedArray<WorldGraphVisualNodeBase> inputNodes)
{
InputNodes = TypedArrayToVector(inputNodes);
RefreshInputs();
}
bool NodeIsValid() const { return IsValid(); }
TypedArray<int> NodeGetInputTypes() const { return VectorToTypedArrayCast<int>(GetInputTypes()); }
int NodeGetOutputType() const { return GetOutputType(); }
void NodeSetInput(int index, Ref<WorldGraphVisualNodeBase> input) { SetInput(index, input); }
bool HasInternalNode() const { return InternalNode.get(); };
bool CanExecuteNode();
void SetInternalNode(std::unique_ptr<WorldNodeBase>&& node);
WorldNodeBase* GetInternalNode() const { return InternalNode.get(); }
void RefreshInputs();
public:
virtual Vector<Variant::Type> GetInputTypes() const { return InternalNode ? InternalNode->GetInputTypes() : Vector<Variant::Type>{}; };
virtual Variant::Type GetOutputType() const { return InternalNode ? InternalNode->GetReturnType() : Variant::Type{}; };
virtual void SetInput(int index, Ref<WorldGraphVisualNodeBase> input)
{
InputNodes.set(index, input);
InternalNode->SetInput(index, input.is_valid() ? input->InternalNode.get() : nullptr);
}
virtual bool IsValid() const
{
if (!InternalNode)
print_error(String("No internal node for ") + get_class_name());
if (!InternalNode->IsValid())
print_error(String("node is invalid ") + get_class_name());
return InternalNode && InternalNode->IsValid();
}
virtual void RefreshValues() {};
public:
Vector2i Position{};
Vector<Ref<WorldGraphVisualNodeBase>> InputNodes{};
private:
std::unique_ptr<WorldNodeBase> InternalNode{};
};
class WorldGraphVisualNode_Math : public WorldGraphVisualNodeBase
{
GDCLASS(WorldGraphVisualNode_Math, WorldGraphVisualNodeBase);
public:
static void _bind_methods();
public:
WorldGraphVisualNode_Math();
virtual ~WorldGraphVisualNode_Math() = default;
public:
TypedArray<String> GetNodeNames() const;
void SetNode(String nodeName);
String GetNode() const { return NodeID; }
private:
String NodeID{};
};
class WorldGraphVisualNode_Constant : public WorldGraphVisualNodeBase
{
GDCLASS(WorldGraphVisualNode_Constant, WorldGraphVisualNodeBase);
public:
static void _bind_methods();
public:
WorldGraphVisualNode_Constant();
virtual ~WorldGraphVisualNode_Constant() = default;
private:
void SetValue(float val);
float GetValue() const;
};
class WorldGraphVisualNode_If : public WorldGraphVisualNodeBase
{
GDCLASS(WorldGraphVisualNode_If, WorldGraphVisualNodeBase);
public:
static void _bind_methods() {};
public:
WorldGraphVisualNode_If();
virtual ~WorldGraphVisualNode_If() = default;
};
class WorldGraphVisualNode_Noise : public WorldGraphVisualNodeBase
{
GDCLASS(WorldGraphVisualNode_Noise, WorldGraphVisualNodeBase);
public:
static void _bind_methods();
public:
void SetNoiseType(String noiseType);
String GetNoiseType() const { return NoiseType; }
float GetFrequency() const { return Frequency; }
TypedArray<String> GetNoiseTypes() const;
void SetFrequency(float val);
virtual void RefreshValues() override;
public:
WorldGraphVisualNode_Noise();
virtual ~WorldGraphVisualNode_Noise() = default;
private:
String NoiseType{};
float Frequency{};
};
class WorldGraphVisualNode_Tile : public WorldGraphVisualNodeBase
{
GDCLASS(WorldGraphVisualNode_Tile, WorldGraphVisualNodeBase);
public:
static void _bind_methods();
public:
WorldGraphVisualNode_Tile();
virtual ~WorldGraphVisualNode_Tile() = default;
public:
void SetType(int type);
void SetRelativeX(int offset);
void SetRelativeY(int offset);
int GetType() const;
int GetRelativeX() const;
int GetRelativeY() const;
};
class WorldGraphVisualNode_TileDistance : public WorldGraphVisualNodeBase
{
GDCLASS(WorldGraphVisualNode_TileDistance, WorldGraphVisualNodeBase);
public:
static void _bind_methods();
public:
WorldGraphVisualNode_TileDistance();
virtual ~WorldGraphVisualNode_TileDistance() = default;
public:
void SetType(int type);
void SetRange(int range);
int GetType() const;
int GetRange() const;
};

View File

@@ -0,0 +1,63 @@
// #pragma once
// #include "Data/Tile.h"
// #include "Data/Recipe.h"
// #include "core/object/ref_counted.h"
// #include "modules/noise/fastnoise_lite.h"
// #include "core/io/resource.h"
// #include "Data/Archetype.h"
// #include "LayerConfigs.h"
// #include "scene/resources/2d/tile_set.h"
// #include "Data/WorldGraph/WorldGraph.h"
// struct FactoryWorldSettings : public Resource
// {
// public:
// GDCLASS(FactoryWorldSettings, Resource);
// public:
// static void _bind_methods();
// public:
// void Merge(Ref<FactoryWorldSettings> settings);
// void Initialize();
// public:
// TypedArray<RecipeConfig> GetRecipes() const { return VectorToTypedArray(Recipes); }
// TypedArray<Archetype> GetPlaceableArchetypes() const { return VectorToTypedArray(PlaceableArchetypes); }
// TypedArray<TileConfig> GetTileConfigs() const { return VectorToTypedArray(TileConfigs); }
// TypedArray<LayerConfig> GetLayerConfigs() const { return VectorToTypedArray(LayerConfigs); }
// void SetRecipes(TypedArray<RecipeConfig> recipes) { Recipes = TypedArrayToVector(recipes); }
// void SetPlaceableArchetypes(TypedArray<PlaceableArchetype> archetypes) { PlaceableArchetypes = TypedArrayToVector(archetypes); }
// void SetTileConfigs(TypedArray<TileConfig> tiles) { TileConfigs = TypedArrayToVector(tiles); }
// void SetLayerConfigs(TypedArray<LayerConfig> layers) { LayerConfigs = TypedArrayToVector(layers); }
// public:
// int GetStartHeight() const;
// Vector<ItemAmount> GetChunkUnlockCosts(int x, int y) const;
// Vector<ItemAmount> GetChunkUnlockCosts(ChunkKey chunk) const;
// Ref<LayerConfig> GetLayer(ChunkKey chunk) const;
// private:
// void InitializeResources();
// void InitializeLayers();
// void InitializeWorldGenerators();
// void InitializeTexturesSheets();
// void InitializeItemGraph();
// public:
// Vector<Ref<TileConfig>> TileConfigs{};
// Vector<Ref<RecipeConfig>> Recipes{};
// Vector<Ref<ItemConfig>> Items{};
// Vector<Ref<Archetype>> Archetypes{};
// Vector<Ref<PlaceableArchetype>> PlaceableArchetypes{};
// Vector<Ref<LayerConfig>> LayerConfigs{};
// HashMap<Ref<ItemConfig>,int> ItemComplexity{};
// Vector<Ref<Texture2D>> TileSheets{};
// Ref<TileSet> TileSet{};
// WorldGraph WorldGenerator{};
// };

837
include/Util/AStar.h Normal file
View File

@@ -0,0 +1,837 @@
/*
A* Algorithm Implementation using STL is
Copyright (C)2001-2005 Justin Heyes-Jones
Permission is given by the author to freely redistribute and
include this code in any program as long as this credit is
given where due.
COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE
IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED
CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL
DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY
NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE
OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
THIS DISCLAIMER.
Use at your own risk!
*/
#ifndef STLASTAR_H
#define STLASTAR_H
// used for text debugging
#include <iostream>
#include <stdio.h>
//#include <conio.h>
#include <assert.h>
// stl includes
#include <algorithm>
#include <unordered_set>
#include <vector>
#include <cfloat>
// fast fixed size memory allocator, used for fast node memory management
#include "fsa.h"
// Fixed size memory allocator can be disabled to compare performance
// Uses std new and delete instead if you turn it off
#define USE_FSA_MEMORY 1
// disable warning that debugging information has lines that are truncated
// occurs in stl headers
#if defined(WIN32) && defined(_WINDOWS)
#pragma warning( disable : 4786 )
#endif
template <class T> class AStarState;
// The AStar search class. UserState is the users state space type
template <class UserState> class AStarSearch
{
public: // data
enum
{
SEARCH_STATE_NOT_INITIALISED,
SEARCH_STATE_SEARCHING,
SEARCH_STATE_SUCCEEDED,
SEARCH_STATE_FAILED,
SEARCH_STATE_OUT_OF_MEMORY,
SEARCH_STATE_INVALID
};
// A node represents a possible state in the search
// The user provided state type is included inside this type
public:
class Node
{
public:
Node* parent; // used during the search to record the parent of successor nodes
Node* child; // used after the search for the application to view the search in reverse
float g; // cost of this node + its predecessors
float h; // heuristic estimate of distance to goal
float f; // sum of cumulative cost of predecessors and self and heuristic
Node() :
parent(0),
child(0),
g(0.0f),
h(0.0f),
f(0.0f)
{
}
bool operator==(const Node& otherNode) const
{
return m_UserState.IsSameState(otherNode.m_UserState);
}
UserState m_UserState;
};
// For sorting the heap the STL needs compare function that lets us compare
// the f value of two nodes
class HeapCompare_f
{
public:
bool operator() (const Node* x, const Node* y) const
{
return x->f > y->f;
}
};
public: // methods
// constructor just initialises private data
AStarSearch() :
m_State(SEARCH_STATE_NOT_INITIALISED),
m_CurrentSolutionNode(NULL),
#if USE_FSA_MEMORY
m_FixedSizeAllocator(1000),
#endif
m_AllocateNodeCount(0),
m_CancelRequest(false)
{
}
AStarSearch(int MaxNodes) :
m_State(SEARCH_STATE_NOT_INITIALISED),
m_CurrentSolutionNode(NULL),
#if USE_FSA_MEMORY
m_FixedSizeAllocator(MaxNodes),
#endif
m_AllocateNodeCount(0),
m_CancelRequest(false)
{
}
// call at any time to cancel the search and free up all the memory
void CancelSearch()
{
m_CancelRequest = true;
}
// Set Start and goal states
void SetStartAndGoalStates(const UserState& Start, const UserState& Goal)
{
m_CancelRequest = false;
m_Start = AllocateNode();
m_Goal = AllocateNode();
assert((m_Start != NULL && m_Goal != NULL));
m_Start->m_UserState = Start;
m_Goal->m_UserState = Goal;
m_State = SEARCH_STATE_SEARCHING;
// Initialise the AStar specific parts of the Start Node
// The user only needs fill out the state information
m_Start->g = 0;
m_Start->h = m_Start->m_UserState.GoalDistanceEstimate(m_Goal->m_UserState);
m_Start->f = m_Start->g + m_Start->h;
m_Start->parent = 0;
// Push the start node on the Open list
m_OpenList.push_back(m_Start); // heap now unsorted
// Sort back element into heap
push_heap(m_OpenList.begin(), m_OpenList.end(), HeapCompare_f());
// Initialise counter for search steps
m_Steps = 0;
}
// Advances search one step
unsigned int SearchStep()
{
// Firstly break if the user has not initialised the search
assert((m_State > SEARCH_STATE_NOT_INITIALISED) &&
(m_State < SEARCH_STATE_INVALID));
// Next I want it to be safe to do a searchstep once the search has succeeded...
if ((m_State == SEARCH_STATE_SUCCEEDED) ||
(m_State == SEARCH_STATE_FAILED)
)
{
return m_State;
}
// Failure is defined as emptying the open list as there is nothing left to
// search...
// New: Allow user abort
if (m_OpenList.empty() || m_CancelRequest)
{
FreeAllNodes();
m_State = SEARCH_STATE_FAILED;
return m_State;
}
// Incremement step count
m_Steps++;
// Pop the best node (the one with the lowest f)
Node* n = m_OpenList.front(); // get pointer to the node
pop_heap(m_OpenList.begin(), m_OpenList.end(), HeapCompare_f());
m_OpenList.pop_back();
// Check for the goal, once we pop that we're done
if (n->m_UserState.IsGoal(m_Goal->m_UserState))
{
// The user is going to use the Goal Node he passed in
// so copy the parent pointer of n
m_Goal->parent = n->parent;
m_Goal->g = n->g;
// A special case is that the goal was passed in as the start state
// so handle that here
if (false == n->m_UserState.IsSameState(m_Start->m_UserState))
{
FreeNode(n);
// set the child pointers in each node (except Goal which has no child)
Node* nodeChild = m_Goal;
Node* nodeParent = m_Goal->parent;
do
{
nodeParent->child = nodeChild;
nodeChild = nodeParent;
nodeParent = nodeParent->parent;
} while (nodeChild != m_Start); // Start is always the first node by definition
}
// delete nodes that aren't needed for the solution
FreeUnusedNodes();
m_State = SEARCH_STATE_SUCCEEDED;
return m_State;
}
else // not goal
{
// We now need to generate the successors of this node
// The user helps us to do this, and we keep the new nodes in
// m_Successors ...
m_Successors.clear(); // empty vector of successor nodes to n
// User provides this functions and uses AddSuccessor to add each successor of
// node 'n' to m_Successors
bool ret = n->m_UserState.GetSuccessors(this, n->parent ? &n->parent->m_UserState : NULL);
if (!ret)
{
typename std::vector<Node*>::iterator successor;
// free the nodes that may previously have been added
for (successor = m_Successors.begin(); successor != m_Successors.end(); successor++)
{
FreeNode((*successor));
}
m_Successors.clear(); // empty vector of successor nodes to n
// free up everything else we allocated
FreeNode((n));
FreeAllNodes();
m_State = SEARCH_STATE_OUT_OF_MEMORY;
return m_State;
}
// Now handle each successor to the current node ...
for (typename std::vector<Node*>::iterator successor = m_Successors.begin(); successor != m_Successors.end(); successor++)
{
// The g value for this successor ...
float newg = n->g + n->m_UserState.GetCost((*successor)->m_UserState);
// Now we need to find whether the node is on the open or closed lists
// If it is but the node that is already on them is better (lower g)
// then we can forget about this successor
// First linear search of open list to find node
typename std::vector<Node*>::iterator openlist_result;
for (openlist_result = m_OpenList.begin(); openlist_result != m_OpenList.end(); openlist_result++)
{
if ((*openlist_result)->m_UserState.IsSameState((*successor)->m_UserState))
{
break;
}
}
if (openlist_result != m_OpenList.end())
{
// we found this state on open
if ((*openlist_result)->g <= newg)
{
FreeNode((*successor));
// the one on Open is cheaper than this one
continue;
}
}
typename std::unordered_set<Node*, NodeHash, NodeEqual>::iterator closedlist_result;
closedlist_result = m_ClosedList.find(*successor);
if (closedlist_result != m_ClosedList.end())
{
// we found this state on closed
if ((*closedlist_result)->g <= newg)
{
// the one on Closed is cheaper than this one
FreeNode((*successor));
continue;
}
}
// This node is the best node so far with this particular state
// so lets keep it and set up its AStar specific data ...
(*successor)->parent = n;
(*successor)->g = newg;
(*successor)->h = (*successor)->m_UserState.GoalDistanceEstimate(m_Goal->m_UserState);
(*successor)->f = (*successor)->g + (*successor)->h;
// Successor in closed list
// 1 - Update old version of this node in closed list
// 2 - Move it from closed to open list
// 3 - Sort heap again in open list
if (closedlist_result != m_ClosedList.end())
{
// Update closed node with successor node AStar data
//*(*closedlist_result) = *(*successor);
(*closedlist_result)->parent = (*successor)->parent;
(*closedlist_result)->g = (*successor)->g;
(*closedlist_result)->h = (*successor)->h;
(*closedlist_result)->f = (*successor)->f;
// Free successor node
FreeNode((*successor));
// Push closed node into open list
m_OpenList.push_back((*closedlist_result));
// Remove closed node from closed list
m_ClosedList.erase(closedlist_result);
// Sort back element into heap
push_heap(m_OpenList.begin(), m_OpenList.end(), HeapCompare_f());
// Fix thanks to ...
// Greg Douglas <gregdouglasmail@gmail.com>
// who noticed that this code path was incorrect
// Here we have found a new state which is already CLOSED
}
// Successor in open list
// 1 - Update old version of this node in open list
// 2 - sort heap again in open list
else if (openlist_result != m_OpenList.end())
{
// Update open node with successor node AStar data
//*(*openlist_result) = *(*successor);
(*openlist_result)->parent = (*successor)->parent;
(*openlist_result)->g = (*successor)->g;
(*openlist_result)->h = (*successor)->h;
(*openlist_result)->f = (*successor)->f;
// Free successor node
FreeNode((*successor));
// re-make the heap
// make_heap rather than sort_heap is an essential bug fix
// thanks to Mike Ryynanen for pointing this out and then explaining
// it in detail. sort_heap called on an invalid heap does not work
make_heap(m_OpenList.begin(), m_OpenList.end(), HeapCompare_f());
}
// New successor
// 1 - Move it from successors to open list
// 2 - sort heap again in open list
else
{
// Push successor node into open list
m_OpenList.push_back((*successor));
// Sort back element into heap
push_heap(m_OpenList.begin(), m_OpenList.end(), HeapCompare_f());
}
}
// push n onto Closed, as we have expanded it now
m_ClosedList.insert(n);
} // end else (not goal so expand)
return m_State; // Succeeded bool is false at this point.
}
// User calls this to add a successor to a list of successors
// when expanding the search frontier
bool AddSuccessor(const UserState& State)
{
Node* node = AllocateNode();
if (node)
{
node->m_UserState = State;
m_Successors.push_back(node);
return true;
}
return false;
}
// Free the solution nodes
// This is done to clean up all used Node memory when you are done with the
// search
void FreeSolutionNodes()
{
Node* n = m_Start;
if (m_Start->child)
{
do
{
Node* del = n;
n = n->child;
FreeNode(del);
del = NULL;
} while (n != m_Goal);
FreeNode(n); // Delete the goal
}
else
{
// if the start node is the solution we need to just delete the start and goal
// nodes
FreeNode(m_Start);
FreeNode(m_Goal);
}
}
// Functions for traversing the solution
// Get start node
UserState* GetSolutionStart()
{
m_CurrentSolutionNode = m_Start;
if (m_Start)
{
return &m_Start->m_UserState;
}
else
{
return NULL;
}
}
// Get next node
UserState* GetSolutionNext()
{
if (m_CurrentSolutionNode)
{
if (m_CurrentSolutionNode->child)
{
Node* child = m_CurrentSolutionNode->child;
m_CurrentSolutionNode = m_CurrentSolutionNode->child;
return &child->m_UserState;
}
}
return NULL;
}
// Get end node
UserState* GetSolutionEnd()
{
m_CurrentSolutionNode = m_Goal;
if (m_Goal)
{
return &m_Goal->m_UserState;
}
else
{
return NULL;
}
}
// Step solution iterator backwards
UserState* GetSolutionPrev()
{
if (m_CurrentSolutionNode)
{
if (m_CurrentSolutionNode->parent)
{
Node* parent = m_CurrentSolutionNode->parent;
m_CurrentSolutionNode = m_CurrentSolutionNode->parent;
return &parent->m_UserState;
}
}
return NULL;
}
// Get final cost of solution
// Returns FLT_MAX if goal is not defined or there is no solution
float GetSolutionCost()
{
if (m_Goal && m_State == SEARCH_STATE_SUCCEEDED)
{
return m_Goal->g;
}
else
{
return FLT_MAX;
}
}
// For educational use and debugging it is useful to be able to view
// the open and closed list at each step, here are two functions to allow that.
UserState* GetOpenListStart()
{
float f, g, h;
return GetOpenListStart(f, g, h);
}
UserState* GetOpenListStart(float& f, float& g, float& h)
{
iterDbgOpen = m_OpenList.begin();
if (iterDbgOpen != m_OpenList.end())
{
f = (*iterDbgOpen)->f;
g = (*iterDbgOpen)->g;
h = (*iterDbgOpen)->h;
return &(*iterDbgOpen)->m_UserState;
}
return NULL;
}
UserState* GetOpenListNext()
{
float f, g, h;
return GetOpenListNext(f, g, h);
}
UserState* GetOpenListNext(float& f, float& g, float& h)
{
iterDbgOpen++;
if (iterDbgOpen != m_OpenList.end())
{
f = (*iterDbgOpen)->f;
g = (*iterDbgOpen)->g;
h = (*iterDbgOpen)->h;
return &(*iterDbgOpen)->m_UserState;
}
return NULL;
}
UserState* GetClosedListStart()
{
float f, g, h;
return GetClosedListStart(f, g, h);
}
UserState* GetClosedListStart(float& f, float& g, float& h)
{
iterDbgClosed = m_ClosedList.begin();
if (iterDbgClosed != m_ClosedList.end())
{
f = (*iterDbgClosed)->f;
g = (*iterDbgClosed)->g;
h = (*iterDbgClosed)->h;
return &(*iterDbgClosed)->m_UserState;
}
return NULL;
}
UserState* GetClosedListNext()
{
float f, g, h;
return GetClosedListNext(f, g, h);
}
UserState* GetClosedListNext(float& f, float& g, float& h)
{
iterDbgClosed++;
if (iterDbgClosed != m_ClosedList.end())
{
f = (*iterDbgClosed)->f;
g = (*iterDbgClosed)->g;
h = (*iterDbgClosed)->h;
return &(*iterDbgClosed)->m_UserState;
}
return NULL;
}
// Get the number of steps
int GetStepCount() { return m_Steps; }
void EnsureMemoryFreed()
{
#if USE_FSA_MEMORY
assert(m_AllocateNodeCount == 0);
#endif
}
private: // methods
// This is called when a search fails or is cancelled to free all used
// memory
void FreeAllNodes()
{
// iterate open list and delete all nodes
typename std::vector<Node*>::iterator iterOpen = m_OpenList.begin();
while (iterOpen != m_OpenList.end())
{
Node* n = (*iterOpen);
FreeNode(n);
iterOpen++;
}
m_OpenList.clear();
// iterate closed list and delete unused nodes
typename std::unordered_set<Node*, NodeHash, NodeEqual>::iterator iterClosed;
for (iterClosed = m_ClosedList.begin(); iterClosed != m_ClosedList.end(); iterClosed++)
{
Node* n = (*iterClosed);
FreeNode(n);
}
m_ClosedList.clear();
// delete the goal
FreeNode(m_Goal);
}
// This call is made by the search class when the search ends. A lot of nodes may be
// created that are still present when the search ends. They will be deleted by this
// routine once the search ends
void FreeUnusedNodes()
{
// iterate open list and delete unused nodes
typename std::vector< Node* >::iterator iterOpen = m_OpenList.begin();
while (iterOpen != m_OpenList.end())
{
Node* n = (*iterOpen);
if (!n->child)
{
FreeNode(n);
n = NULL;
}
iterOpen++;
}
m_OpenList.clear();
// iterate closed list and delete unused nodes
typename std::unordered_set<Node*, NodeHash, NodeEqual>::iterator iterClosed;
for (iterClosed = m_ClosedList.begin(); iterClosed != m_ClosedList.end(); iterClosed++)
{
Node* n = (*iterClosed);
if (!n->child)
{
FreeNode(n);
n = NULL;
}
}
m_ClosedList.clear();
}
// Node memory management
Node* AllocateNode()
{
#if !USE_FSA_MEMORY
m_AllocateNodeCount++;
Node* p = new Node;
return p;
#else
Node* address = m_FixedSizeAllocator.alloc();
if (!address)
{
return NULL;
}
m_AllocateNodeCount++;
Node* p = new (address) Node;
return p;
#endif
}
void FreeNode(Node* node)
{
m_AllocateNodeCount--;
#if !USE_FSA_MEMORY
delete node;
#else
node->~Node();
m_FixedSizeAllocator.free(node);
#endif
}
private: // data
// Heap (simple vector but used as a heap, cf. Steve Rabin's game gems article)
std::vector<Node*> m_OpenList;
// Closed is an unordered_set
struct NodeHash {
size_t operator() (Node* const& n) const {
return n->m_UserState.Hash();
}
};
struct NodeEqual {
bool operator()(Node* a, Node* b) const {
return a->m_UserState.IsSameState(b->m_UserState);
}
};
std::unordered_set<Node*, NodeHash, NodeEqual> m_ClosedList;
// Successors is a vector filled out by the user each type successors to a node
// are generated
std::vector< Node* > m_Successors;
// State
unsigned int m_State;
// Counts steps
int m_Steps;
// Start and goal state pointers
Node* m_Start;
Node* m_Goal;
Node* m_CurrentSolutionNode;
#if USE_FSA_MEMORY
// Memory
FixedSizeAllocator<Node> m_FixedSizeAllocator;
#endif
//Debug : need to keep these two iterators around
// for the user Dbg functions
typename std::vector< Node* >::iterator iterDbgOpen;
typename std::vector< Node* >::iterator iterDbgClosed;
// debugging : count memory allocation and free's
int m_AllocateNodeCount;
bool m_CancelRequest;
};
template <class T> class AStarState
{
public:
virtual ~AStarState() {}
virtual float GoalDistanceEstimate(T& nodeGoal) = 0; // Heuristic function which computes the estimated cost to the goal node
virtual bool IsGoal(T& nodeGoal) = 0; // Returns true if this node is the goal node
virtual bool GetSuccessors(AStarSearch<T>* astarsearch, T* parent_node) = 0; // Retrieves all successors to this node and adds them via astarsearch.addSuccessor()
virtual float GetCost(T& successor) = 0; // Computes the cost of travelling from this node to the successor node
virtual bool IsSameState(T& rhs) = 0; // Returns true if this node is the same as the rhs node
virtual size_t Hash() = 0; // Returns a hash for the state
};
#endif

1191
include/Util/FastNoiseLite.h Normal file

File diff suppressed because it is too large Load Diff

208
include/Util/Helpers.h Normal file
View File

@@ -0,0 +1,208 @@
#pragma once
#include "core/templates/vector.h"
#include "core/object/ref_counted.h"
#include "core/variant/variant.h"
#include "core/variant/typed_array.h"
template <typename T>
TypedArray<T> VectorToTypedArray(const Vector<Ref<T>>& vector)
{
TypedArray<T> arr;
arr.resize(vector.size());
for (int i{}; i < vector.size(); ++i)
{
arr[i] = Variant(vector[i]);
}
return arr;
}
template <typename T>
Vector<Ref<T>> TypedArrayToVector(const TypedArray<T>& arr)
{
Vector<Ref<T>> vector;
vector.resize(arr.size());
for (int i{}; i < vector.size(); ++i)
{
vector.set(i, Ref<T>(arr[i]));
}
return vector;
}
template <typename T>
TypedArray<T> VectorToTypedArrayVariant(const Vector<T>& vector)
{
TypedArray<T> arr;
arr.resize(vector.size());
for (int i{}; i < vector.size(); ++i)
{
arr[i] = Variant(vector[i]);
}
return arr;
}
template <typename T>
Vector<T> TypedArrayToVectorVariant(const TypedArray<T>& arr)
{
Vector<T> vector;
vector.resize(arr.size());
for (int i{}; i < vector.size(); ++i)
{
vector.set(i, arr[i]);
}
return vector;
}
template <typename To, typename From>
TypedArray<To> VectorToTypedArrayCast(const Vector<From>& vector)
{
TypedArray<To> arr;
arr.resize(vector.size());
for (int i{}; i < vector.size(); ++i)
{
arr[i] = Variant(static_cast<To>(vector[i]));
}
return arr;
}
// template <typename T>
// static Type get_type_t();
// template <> static Type get_type_t<bool>() { return Type::BOOL; }
// template <> static Type get_type_t<int>() { return Type::INT; }
// template <> static Type get_type_t<float>() { return Type::FLOAT; }
// template <> static Type get_type_t<String>() { return Type::STRING; }
// template <> static Type get_type_t<Vector2>() { return Type::VECTOR2; }
// template <> static Type get_type_t<Vector2i>() { return Type::VECTOR2I; }
// template <> static Type get_type_t<Rect2>() { return Type::RECT2; }
// template <> static Type get_type_t<Rect2i>() { return Type::RECT2I; }
// template <> static Type get_type_t<Vector3>() { return Type::VECTOR3; }
// template <> static Type get_type_t<Vector3i>() { return Type::VECTOR3I; }
// template <> static Type get_type_t<Transform2D>() { return Type::TRANSFORM2D; }
// template <> static Type get_type_t<Vector4>() { return Type::VECTOR4; }
// template <> static Type get_type_t<Vector4i>() { return Type::VECTOR4I; }
// template <> static Type get_type_t<Plane>() { return Type::PLANE; }
// template <> static Type get_type_t<Quaternion>() { return Type::QUATERNION; }
// template <> static Type get_type_t<::AABB>() { return Type::AABB; }
// template <> static Type get_type_t<Basis>() { return Type::BASIS; }
// template <> static Type get_type_t<Transform3D>() { return Type::TRANSFORM3D; }
// template <> static Type get_type_t<Projection>() { return Type::PROJECTION; }
// template <> static Type get_type_t<Color>() { return Type::COLOR; }
// template <> static Type get_type_t<StringName>() { return Type::STRING_NAME; }
// template <> static Type get_type_t<NodePath>() { return Type::NODE_PATH; }
// template <> static Type get_type_t<::RID>() { return Type::RID; }
// template <> static Type get_type_t<Object*>() { return Type::OBJECT; }
// template <> static Type get_type_t<Callable>() { return Type::CALLABLE; }
// template <> static Type get_type_t<Signal>() { return Type::SIGNAL; }
// template <> static Type get_type_t<Dictionary>() { return Type::DICTIONARY; }
// template <> static Type get_type_t<Array>() { return Type::ARRAY; }
// template <> static Type get_type_t<PackedByteArray>() { return Type::PACKED_BYTE_ARRAY; }
// template <> static Type get_type_t<PackedInt32Array>() { return Type::PACKED_INT32_ARRAY; }
// template <> static Type get_type_t<PackedInt64Array>() { return Type::PACKED_INT64_ARRAY; }
// template <> static Type get_type_t<PackedFloat32Array>() { return Type::PACKED_FLOAT32_ARRAY; }
// template <> static Type get_type_t<PackedFloat64Array>() { return Type::PACKED_FLOAT64_ARRAY; }
// template <> static Type get_type_t<PackedStringArray>() { return Type::PACKED_STRING_ARRAY; }
// template <> static Type get_type_t<PackedVector2Array>() { return Type::PACKED_VECTOR2_ARRAY; }
// template <> static Type get_type_t<PackedVector3Array>() { return Type::PACKED_VECTOR3_ARRAY; }
// template <> static Type get_type_t<PackedColorArray>() { return Type::PACKED_COLOR_ARRAY; }
// template <> static Type get_type_t<PackedVector4Array>() { return Type::PACKED_VECTOR4_ARRAY; }
// //template <typename T> static Type get_type_t() { return Type::NIL; }
// bool get_unsafe_bool() const { DEV_ASSERT(type == Type::BOOL); return _data._bool; }
// int get_unsafe_int() const { DEV_ASSERT(type == Type::INT); return _data._int; }
// float get_unsafe_float() const { DEV_ASSERT(type == Type::FLOAT); return _data._float; }
// String get_unsafe_string() const { DEV_ASSERT(type == Type::STRING); return *reinterpret_cast<const String *>(_data._mem); }
// Vector2 get_unsafe_vector2() const { DEV_ASSERT(type == Type::VECTOR2); return *reinterpret_cast<const Vector2 *>(_data._mem); }
// Vector2i get_unsafe_vector2i() const { DEV_ASSERT(type == Type::VECTOR2I); return *reinterpret_cast<const Vector2i *>(_data._mem); }
// Vector3 get_unsafe_vector3() const { DEV_ASSERT(type == Type::VECTOR3); return *reinterpret_cast<const Vector3 *>(_data._mem); }
// Vector3i get_unsafe_vector3i() const { DEV_ASSERT(type == Type::VECTOR3I); return *reinterpret_cast<const Vector3i *>(_data._mem); }
// Transform2D get_unsafe_transform2d() const { DEV_ASSERT(type == Type::TRANSFORM2D); return *reinterpret_cast<const Transform2D *>(_data._mem); }
// Vector4 get_unsafe_vector4() const { DEV_ASSERT(type == Type::VECTOR4); return *reinterpret_cast<const Vector4 *>(_data._mem); }
// Vector4i get_unsafe_vector4i() const { DEV_ASSERT(type == Type::VECTOR4I); return *reinterpret_cast<const Vector4i *>(_data._mem); }
// Plane get_unsafe_plane() const { DEV_ASSERT(type == Type::PLANE); return *reinterpret_cast<const Plane *>(_data._mem); }
// Quaternion get_unsafe_quaternion() const { DEV_ASSERT(type == Type::QUATERNION); return *reinterpret_cast<const Quaternion *>(_data._mem); }
// ::AABB get_unsafe_aabb() const { DEV_ASSERT(type == Type::AABB); return *reinterpret_cast<const ::AABB *>(_data._mem); }
// Basis get_unsafe_basis() const { DEV_ASSERT(type == Type::BASIS); return *reinterpret_cast<const Basis *>(_data._mem); }
// Transform3D get_unsafe_transform3d() const { DEV_ASSERT(type == Type::TRANSFORM3D); return *reinterpret_cast<const Transform3D *>(_data._mem); }
// Projection get_unsafe_projection() const { DEV_ASSERT(type == Type::PROJECTION); return *reinterpret_cast<const Projection *>(_data._mem); }
// Color get_unsafe_color() const { DEV_ASSERT(type == Type::COLOR); return *reinterpret_cast<const Color *>(_data._mem); }
// StringName get_unsafe_string_name() const { DEV_ASSERT(type == Type::STRING_NAME); return *reinterpret_cast<const StringName *>(_data._mem); }
// NodePath get_unsafe_node_path() const { DEV_ASSERT(type == Type::NODE_PATH); return *reinterpret_cast<const NodePath *>(_data._mem); }
// ::RID get_unsafe_rid() const { DEV_ASSERT(type == Type::RID); return *reinterpret_cast<const ::RID *>(_data._mem); }
// Object* get_unsafe_object() const { DEV_ASSERT(type == Type::OBJECT); return reinterpret_cast<const ObjData *>(&_data._mem[0])->obj; }
// Callable get_unsafe_callable() const { DEV_ASSERT(type == Type::CALLABLE); return *reinterpret_cast<const Callable *>(_data._mem); }
// Signal get_unsafe_signal() const { DEV_ASSERT(type == Type::SIGNAL); return *reinterpret_cast<const Signal *>(_data._mem); }
// Dictionary get_unsafe_dictionary() const { DEV_ASSERT(type == Type::DICTIONARY); return *reinterpret_cast<const Dictionary *>(_data._mem); }
// Array get_unsafe_array() const { DEV_ASSERT(type == Type::ARRAY); return *reinterpret_cast<const Array *>(_data._mem); }
// PackedByteArray get_unsafe_packed_byte_array() const { DEV_ASSERT(type == Type::PACKED_BYTE_ARRAY); return static_cast<PackedArrayRef<uint8_t> *>(_data.packed_array)->array; }
// PackedInt32Array get_unsafe_packed_int32_array() const { DEV_ASSERT(type == Type::PACKED_INT32_ARRAY); return static_cast<PackedArrayRef<int32_t> *>(_data.packed_array)->array; }
// PackedInt64Array get_unsafe_packed_int64_array() const { DEV_ASSERT(type == Type::PACKED_INT64_ARRAY); return static_cast<PackedArrayRef<int64_t> *>(_data.packed_array)->array; }
// PackedFloat32Array get_unsafe_packed_float32_array() const { DEV_ASSERT(type == Type::PACKED_FLOAT32_ARRAY); return static_cast<PackedArrayRef<float> *>(_data.packed_array)->array; }
// PackedFloat64Array get_unsafe_packed_float64_array() const { DEV_ASSERT(type == Type::PACKED_FLOAT64_ARRAY); return static_cast<PackedArrayRef<double> *>(_data.packed_array)->array; }
// PackedStringArray get_unsafe_packed_string_array() const { DEV_ASSERT(type == Type::PACKED_STRING_ARRAY); return static_cast<PackedArrayRef<String> *>(_data.packed_array)->array; }
// PackedVector2Array get_unsafe_packed_vector2_array() const { DEV_ASSERT(type == Type::PACKED_VECTOR2_ARRAY); return static_cast<PackedArrayRef<Vector2> *>(_data.packed_array)->array; }
// PackedVector3Array get_unsafe_packed_vector3_array() const { DEV_ASSERT(type == Type::PACKED_VECTOR3_ARRAY); return static_cast<PackedArrayRef<Vector3> *>(_data.packed_array)->array; }
// PackedColorArray get_unsafe_packed_color_array() const { DEV_ASSERT(type == Type::PACKED_COLOR_ARRAY); return static_cast<PackedArrayRef<Color> *>(_data.packed_array)->array; }
// PackedVector4Array get_unsafe_packed_vector4_array() const { DEV_ASSERT(type == Type::PACKED_VECTOR4_ARRAY); return static_cast<PackedArrayRef<Vector4> *>(_data.packed_array)->array; }
// template <typename T>
// T get_unsafe_t() const;
// template <> bool get_unsafe_t<bool>() const { return get_unsafe_bool(); }
// template <> int get_unsafe_t<int>() const { return get_unsafe_int(); }
// template <> float get_unsafe_t<float>() const { return get_unsafe_float(); }
// template <> String get_unsafe_t<String>() const { return get_unsafe_string(); }
// template <> Vector2 get_unsafe_t<Vector2>() const { return get_unsafe_vector2(); }
// template <> Vector2i get_unsafe_t<Vector2i>() const { return get_unsafe_vector2i(); }
// template <> Vector3 get_unsafe_t<Vector3>() const { return get_unsafe_vector3(); }
// template <> Vector3i get_unsafe_t<Vector3i>() const { return get_unsafe_vector3i(); }
// template <> Transform2D get_unsafe_t<Transform2D>() const { return get_unsafe_transform2d(); }
// template <> Vector4 get_unsafe_t<Vector4>() const { return get_unsafe_vector4(); }
// template <> Vector4i get_unsafe_t<Vector4i>() const { return get_unsafe_vector4i(); }
// template <> Plane get_unsafe_t<Plane>() const { return get_unsafe_plane(); }
// template <> Quaternion get_unsafe_t<Quaternion>() const { return get_unsafe_quaternion(); }
// template <> ::AABB get_unsafe_t<::AABB>() const { return get_unsafe_aabb(); }
// template <> Basis get_unsafe_t<Basis>() const { return get_unsafe_basis(); }
// template <> Transform3D get_unsafe_t<Transform3D>() const { return get_unsafe_transform3d(); }
// template <> Projection get_unsafe_t<Projection>() const { return get_unsafe_projection(); }
// template <> Color get_unsafe_t<Color>() const { return get_unsafe_color(); }
// template <> StringName get_unsafe_t<StringName>() const { return get_unsafe_string_name(); }
// template <> NodePath get_unsafe_t<NodePath>() const { return get_unsafe_node_path(); }
// template <> ::RID get_unsafe_t<::RID>() const { return get_unsafe_rid(); }
// template <> Object* get_unsafe_t<Object*>() const { return get_unsafe_object(); }
// template <> Callable get_unsafe_t<Callable>() const { return get_unsafe_callable(); }
// template <> Signal get_unsafe_t<Signal>() const { return get_unsafe_signal(); }
// template <> Dictionary get_unsafe_t<Dictionary>() const { return get_unsafe_dictionary(); }
// template <> Array get_unsafe_t<Array>() const { return get_unsafe_array(); }
// template <> PackedByteArray get_unsafe_t<PackedByteArray>() const { return get_unsafe_packed_byte_array(); }
// template <> PackedInt32Array get_unsafe_t<PackedInt32Array>() const { return get_unsafe_packed_int32_array(); }
// template <> PackedInt64Array get_unsafe_t<PackedInt64Array>() const { return get_unsafe_packed_int64_array(); }
// template <> PackedFloat32Array get_unsafe_t<PackedFloat32Array>() const { return get_unsafe_packed_float32_array(); }
// template <> PackedFloat64Array get_unsafe_t<PackedFloat64Array>() const { return get_unsafe_packed_float64_array(); }
// template <> PackedStringArray get_unsafe_t<PackedStringArray>() const { return get_unsafe_packed_string_array(); }
// template <> PackedVector2Array get_unsafe_t<PackedVector2Array>() const { return get_unsafe_packed_vector2_array(); }
// template <> PackedVector3Array get_unsafe_t<PackedVector3Array>() const { return get_unsafe_packed_vector3_array(); }
// template <> PackedColorArray get_unsafe_t<PackedColorArray>() const { return get_unsafe_packed_color_array(); }
// template <> PackedVector4Array get_unsafe_t<PackedVector4Array>() const { return get_unsafe_packed_vector4_array(); }

View File

@@ -0,0 +1,732 @@
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>
*/
// Original: https://github.com/TedLyngmo/inplace_vector
// NOLINTNEXTLINE(llvm-header-guard)
#ifndef LYNIPV_F4BA9AA8_99CD_11EF_8916_90B11C0C0FF8
#define LYNIPV_F4BA9AA8_99CD_11EF_8916_90B11C0C0FF8
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <memory>
#include <new>
#if __cplusplus >= 202002L
# include <ranges>
#endif
#include <stdexcept>
#include <type_traits>
#include <utility>
#if __cplusplus >= 201402L
# define LYNIPV_CXX14_CONSTEXPR constexpr
#else
# define LYNIPV_CXX14_CONSTEXPR
#endif
#if __cplusplus >= 202002L
# define LYNIPV_CXX20_CONSTEXPR constexpr
# define LYNIPV_CONSTRUCT_AT(p, ...) std::construct_at(p __VA_OPT__(, ) __VA_ARGS__)
#else
# define LYNIPV_CXX20_CONSTEXPR
# define LYNIPV_CONSTRUCT_AT(p, ...) ::new(static_cast<void*>(p)) T(__VA_ARGS__)
#endif
namespace lyn {
template<class, std::size_t>
class inplace_vector;
namespace lyn_inplace_vector_detail {
#if __cplusplus >= 201703L
using std::is_nothrow_swappable;
#else
template<typename U>
struct is_nothrow_swappable : std::integral_constant<bool, noexcept(swap(std::declval<U&>(), std::declval<U&>()))> {};
#endif
#if __cplusplus >= 202002L
template<class R, class T>
concept container_compatiblel_range = std::ranges::input_range<R> && std::convertible_to<std::ranges::range_reference_t<R>, T>;
#endif
template<class T, std::size_t N>
struct aligned_storage_non_trivial {
constexpr aligned_storage_non_trivial() noexcept {}
using value_type = typename std::remove_const<T>::type;
using size_type = std::size_t;
using reference = value_type&;
using const_reference = value_type const&;
using pointer = value_type*;
using const_pointer = value_type const*;
// destructor
LYNIPV_CXX20_CONSTEXPR ~aligned_storage_non_trivial() noexcept { static_cast<inplace_vector<T, N>*>(this)->clear(); }
LYNIPV_CXX14_CONSTEXPR pointer ptr(size_type idx) noexcept { return std::addressof(m_data[idx].data); }
LYNIPV_CXX14_CONSTEXPR const_pointer ptr(size_type idx) const noexcept { return std::addressof(m_data[idx].data); }
LYNIPV_CXX14_CONSTEXPR reference ref(size_type idx) noexcept { return m_data[idx].data; }
LYNIPV_CXX14_CONSTEXPR const_reference ref(size_type idx) const noexcept { return m_data[idx].data; }
template<class... Args>
LYNIPV_CXX20_CONSTEXPR reference construct(size_type idx, Args&&... args) {
return *LYNIPV_CONSTRUCT_AT(ptr(idx), std::forward<Args>(args)...);
}
LYNIPV_CXX14_CONSTEXPR void destroy(size_type idx) noexcept { ref(idx).~T(); }
LYNIPV_CXX14_CONSTEXPR reference operator[](size_type idx) noexcept { return ref(idx); }
constexpr const_reference operator[](size_type idx) const noexcept { return ref(idx); }
constexpr size_type size() const noexcept { return m_size; }
LYNIPV_CXX14_CONSTEXPR size_type inc() noexcept { return ++m_size; }
LYNIPV_CXX14_CONSTEXPR size_type dec(size_type count = 1) noexcept { return m_size -= count; }
private:
union raw {
LYNIPV_CXX20_CONSTEXPR ~raw() {}
char dummy{};
value_type data;
};
std::array<raw, N> m_data;
static_assert(sizeof m_data == sizeof(T[N]), "erroneous size");
size_type m_size = 0;
};
template<class T, std::size_t N>
struct aligned_storage_trivial {
static_assert(std::is_trivially_destructible<T>::value, "T must be trivially destructible");
constexpr aligned_storage_trivial() noexcept {}
using value_type = typename std::remove_const<T>::type;
using size_type = std::size_t;
using reference = value_type&;
using const_reference = value_type const&;
using pointer = value_type*;
using const_pointer = value_type const*;
LYNIPV_CXX14_CONSTEXPR pointer ptr(size_type idx) noexcept { return std::addressof(m_data[idx].data); }
LYNIPV_CXX14_CONSTEXPR const_pointer ptr(size_type idx) const noexcept { return std::addressof(m_data[idx].data); }
LYNIPV_CXX14_CONSTEXPR reference ref(size_type idx) noexcept { return m_data[idx].data; }
LYNIPV_CXX14_CONSTEXPR const_reference ref(size_type idx) const noexcept { return m_data[idx].data; }
template<class... Args>
LYNIPV_CXX20_CONSTEXPR reference construct(size_type idx, Args&&... args) {
return *LYNIPV_CONSTRUCT_AT(ptr(idx), std::forward<Args>(args)...);
}
LYNIPV_CXX14_CONSTEXPR void destroy(size_type idx) noexcept { ref(idx).~T(); }
LYNIPV_CXX14_CONSTEXPR reference operator[](size_type idx) noexcept { return ref(idx); }
constexpr const_reference operator[](size_type idx) const noexcept { return ref(idx); }
constexpr size_type size() const noexcept { return m_size; }
LYNIPV_CXX14_CONSTEXPR size_type inc() noexcept { return ++m_size; }
LYNIPV_CXX14_CONSTEXPR size_type dec(size_type count = 1) noexcept { return m_size -= count; }
private:
union raw {
constexpr raw() : dummy{} {}
char dummy;
value_type data;
};
std::array<raw, N> m_data;
static_assert(sizeof m_data == sizeof(T[N]), "erroneous size");
size_type m_size = 0;
};
template<class T>
struct aligned_storage_empty { // specialization for 0 elements
using value_type = typename std::remove_const<T>::type;
using size_type = std::size_t;
using reference = value_type&;
using const_reference = value_type const&;
using pointer = value_type*;
using const_pointer = value_type const*;
LYNIPV_CXX14_CONSTEXPR pointer ptr(size_type) { return nullptr; }
LYNIPV_CXX14_CONSTEXPR const_pointer ptr(size_type) const { return nullptr; }
LYNIPV_CXX14_CONSTEXPR reference ref(size_type) { return *ptr(0); }
LYNIPV_CXX14_CONSTEXPR const_reference ref(size_type) const { return *ptr(0); }
template<class... Args>
LYNIPV_CXX20_CONSTEXPR reference construct(size_type, Args&&...) {
return *ptr(0);
}
LYNIPV_CXX14_CONSTEXPR void destroy(size_type) {}
LYNIPV_CXX14_CONSTEXPR reference operator[](size_type) { return *ptr(0); }
constexpr const_reference operator[](size_type) const { return *ptr(0); }
constexpr size_type size() const noexcept { return 0; }
LYNIPV_CXX14_CONSTEXPR size_type inc() { return 0; }
LYNIPV_CXX14_CONSTEXPR size_type dec(size_type = 1) { return 0; }
};
template<class T, std::size_t N>
struct base_selector {
using type =
typename std::conditional<N == 0, aligned_storage_empty<T>,
typename std::conditional<std::is_trivially_copyable<T>::value, aligned_storage_trivial<T, N>,
aligned_storage_non_trivial<T, N>>::type>::type;
};
} // namespace lyn_inplace_vector_detail
template<class T, std::size_t N>
class inplace_vector : public lyn_inplace_vector_detail::base_selector<T, N>::type {
static_assert(std::is_nothrow_destructible<T>::value,
"inplace_vector: classes with potentially throwing destructors are prohibited");
using base = typename lyn_inplace_vector_detail::base_selector<T, N>::type;
using base::construct;
using base::destroy;
using base::ptr;
using base::ref;
public:
using base::size;
using base::operator[];
using value_type = T;
using size_type = std::size_t;
using reference = T&;
using const_reference = T const&;
using pointer = T*;
using const_pointer = T const*;
using iterator = T*;
using const_iterator = T const*;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using difference_type = typename std::iterator_traits<iterator>::difference_type;
private:
LYNIPV_CXX14_CONSTEXPR void shrink_to(const size_type count) noexcept {
while (count != size()) {
pop_back();
}
}
public:
// constructors
constexpr inplace_vector() noexcept = default;
template<bool D = std::is_default_constructible<T>::value, typename std::enable_if<D, int>::type = 0>
LYNIPV_CXX14_CONSTEXPR explicit inplace_vector(size_type count) {
if (count > N) throw std::bad_alloc();
while (count != size()) unchecked_emplace_back();
}
template<bool C = std::is_copy_constructible<T>::value, typename std::enable_if<C, int>::type = 0>
LYNIPV_CXX14_CONSTEXPR inplace_vector(size_type count, const T& value) {
if (count > N) throw std::bad_alloc();
while (count != size()) unchecked_push_back(value);
}
template<class InputIt, typename std::enable_if<
std::is_constructible<typename std::iterator_traits<InputIt>::value_type>::value, int>::type = 0>
LYNIPV_CXX14_CONSTEXPR inplace_vector(InputIt first, InputIt last) {
std::copy(first, last, std::back_inserter(*this));
}
LYNIPV_CXX14_CONSTEXPR inplace_vector(const inplace_vector& other) = default; // for trivial types
template<class U = T,
typename std::enable_if<std::is_copy_constructible<U>::value &&
not std::is_trivially_copy_constructible<typename std::remove_reference<T>::type>::value,
int>::type = 0>
LYNIPV_CXX14_CONSTEXPR inplace_vector(const inplace_vector& other) {
for (size_type idx = 0; idx != other.size(); ++idx) {
unchecked_push_back(other[idx]);
}
}
LYNIPV_CXX14_CONSTEXPR inplace_vector(inplace_vector&& other) noexcept = default; // for trivial types
template<class U = T,
typename std::enable_if<std::is_move_constructible<U>::value &&
not std::is_trivially_move_constructible<typename std::remove_reference<T>::type>::value,
int>::type = 0>
LYNIPV_CXX14_CONSTEXPR inplace_vector(inplace_vector&& other) noexcept(N == 0 || std::is_nothrow_move_constructible<T>::value) {
for (size_type idx = 0; idx != other.size(); ++idx) {
unchecked_push_back(std::move(other[idx]));
}
other.clear();
}
template<bool C = std::is_copy_constructible<T>::value, typename std::enable_if<C, int>::type = 0>
constexpr inplace_vector(std::initializer_list<T> init) : inplace_vector(init.begin(), init.end()) {}
#if __cplusplus >= 202302L && defined(__cpp_lib_containers_ranges)
template<lyn_inplace_vector_detail::container_compatiblel_range<T> R>
constexpr inplace_vector(std::from_range_t, R&& rg) {
if constexpr (std::ranges::sized_range<R>) {
if (std::ranges::size(rg) > N) throw std::bad_alloc();
for (auto&& val : rg) unchecked_emplace_back(std::forward<decltype(val)>(val));
}
else {
for (auto&& val : rg) emplace_back(std::forward<decltype(val)>(val));
}
}
#endif
// assignment
LYNIPV_CXX14_CONSTEXPR inplace_vector& operator=(const inplace_vector& other) = default; // for trivial types
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto operator=(const inplace_vector& other) ->
typename std::enable_if<not(std::is_trivially_destructible<U>::value&& std::is_trivially_copy_constructible<U>::value&&
std::is_trivially_copy_assignable<U>::value),
inplace_vector&>::type {
assign(other.begin(), other.end());
return *this;
}
LYNIPV_CXX14_CONSTEXPR inplace_vector& operator=(inplace_vector&& other) noexcept(
N == 0 || (std::is_nothrow_move_assignable<T>::value)) = default; // for trivial types
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto operator=(inplace_vector&& other) noexcept(
N == 0 || (std::is_nothrow_move_assignable<T>::value && std::is_nothrow_move_constructible<T>::value &&
not std::is_trivially_copyable<typename std::remove_reference<T>::type>::value)) ->
typename std::enable_if<not(std::is_trivially_destructible<U>::value&& std::is_trivially_move_constructible<U>::value&&
std::is_trivially_move_assignable<U>::value),
inplace_vector&>::type {
clear();
std::move(other.begin(), other.end(), std::back_inserter(*this));
other.clear();
return *this;
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto operator=(std::initializer_list<T> init) ->
typename std::enable_if<std::is_copy_constructible<U>::value, inplace_vector&>::type {
if (init.size() > capacity()) throw std::bad_alloc();
assign(init.begin(), init.end());
return *this;
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto assign(size_type count, const T& value) ->
typename std::enable_if<std::is_copy_constructible<U>::value>::type {
if (count > capacity()) throw std::bad_alloc();
clear();
while (count != size()) push_back(value);
}
template<class InputIt>
LYNIPV_CXX14_CONSTEXPR auto assign(InputIt first, InputIt last) ->
typename std::enable_if<std::is_constructible<T, typename std::iterator_traits<InputIt>::value_type>::value>::type {
clear();
std::copy(first, last, std::back_inserter(*this));
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto assign(std::initializer_list<T> ilist) ->
typename std::enable_if<std::is_copy_constructible<U>::value>::type {
if (ilist.size() > capacity()) throw std::bad_alloc();
clear();
std::copy(ilist.begin(), ilist.end(), std::back_inserter(*this));
}
#if __cplusplus >= 202002L
template<lyn_inplace_vector_detail::container_compatiblel_range<T> R>
constexpr void assign_range(R&& rg)
requires std::constructible_from<T&, std::ranges::range_reference_t<R>>
{
clear();
append_range(std::forward<R>(rg));
}
template<lyn_inplace_vector_detail::container_compatiblel_range<T> R>
constexpr void append_range(R&& rg)
requires std::constructible_from<T&, std::ranges::range_reference_t<R>>
{
if constexpr (std::ranges::sized_range<R>) {
if (size() + std::ranges::size(rg) > capacity()) throw std::bad_alloc();
for (auto&& val : rg) {
unchecked_emplace_back(std::forward<decltype(val)>(val));
}
}
else {
for (auto&& val : rg) {
emplace_back(std::forward<decltype(val)>(val));
}
}
}
template<lyn_inplace_vector_detail::container_compatiblel_range<T> R>
constexpr std::ranges::borrowed_iterator_t<R> try_append_range(R&& rg)
requires std::constructible_from<T&, std::ranges::range_reference_t<R>>
{
auto it = std::ranges::begin(rg);
for (auto end = std::ranges::end(rg); it != end; std::ranges::advance(it, 1)) {
if (size() == capacity()) break;
unchecked_emplace_back(*it);
}
return it;
}
#endif
// element access
LYNIPV_CXX14_CONSTEXPR reference at(size_type idx) {
if (idx >= size()) throw std::out_of_range("");
return ref(idx);
}
LYNIPV_CXX14_CONSTEXPR const_reference at(size_type idx) const {
if (idx >= size()) throw std::out_of_range("");
return ref(idx);
}
LYNIPV_CXX14_CONSTEXPR reference front() noexcept { return ref(0); }
constexpr const_reference front() const noexcept { return ref(0); }
LYNIPV_CXX14_CONSTEXPR reference back() noexcept { return ref(size() - 1); }
constexpr const_reference back() const noexcept { return ref(size() - 1); }
LYNIPV_CXX14_CONSTEXPR pointer data() noexcept { return ptr(0); }
LYNIPV_CXX14_CONSTEXPR const_pointer data() const noexcept { return ptr(0); }
// iterators
constexpr const_iterator cbegin() const noexcept { return data(); }
constexpr const_iterator cend() const noexcept { return std::next(cbegin(), static_cast<difference_type>(size())); }
constexpr const_iterator begin() const noexcept { return cbegin(); }
constexpr const_iterator end() const noexcept { return cend(); }
LYNIPV_CXX14_CONSTEXPR iterator begin() noexcept { return data(); }
LYNIPV_CXX14_CONSTEXPR iterator end() noexcept { return std::next(begin(), static_cast<difference_type>(size())); }
constexpr const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(cend()); }
constexpr const_reverse_iterator crend() const noexcept { return const_reverse_iterator(cbegin()); }
constexpr const_reverse_iterator rbegin() const noexcept { return crbegin(); }
constexpr const_reverse_iterator rend() const noexcept { return crend(); }
LYNIPV_CXX14_CONSTEXPR reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
LYNIPV_CXX14_CONSTEXPR reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
// size and capacity
constexpr bool empty() const noexcept { return size() == 0; }
static constexpr size_type max_size() noexcept { return N; }
static constexpr size_type capacity() noexcept { return N; }
private:
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto unchecked_resize(size_type count) ->
typename std::enable_if<std::is_default_constructible<U>::value>::type {
if (count < size()) {
shrink_to(count);
}
else {
while (count != size()) {
unchecked_emplace_back();
}
}
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto unchecked_resize(size_type count, const value_type& value) ->
typename std::enable_if<std::is_copy_constructible<U>::value>::type {
if (count < size()) {
shrink_to(count);
}
else {
while (count != size()) {
unchecked_push_back(value);
}
}
}
public:
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto resize(size_type count) -> typename std::enable_if<std::is_default_constructible<U>::value>::type {
if (count > capacity()) throw std::bad_alloc();
unchecked_resize(count);
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto resize(size_type count, const value_type& value) ->
typename std::enable_if<std::is_copy_constructible<U>::value>::type {
if (count > capacity()) throw std::bad_alloc();
unchecked_resize(count, value);
}
static LYNIPV_CXX14_CONSTEXPR void reserve(size_type new_cap) {
if (new_cap > capacity()) throw std::bad_alloc();
}
static LYNIPV_CXX14_CONSTEXPR void shrink_to_fit() noexcept {}
// modifiers
private:
/*
// optimization idea for all insert() functions to get away from constructing and rotating:
LYNIPV_CXX14_CONSTEXPR size_type make_room_at(const_iterator pos, size_type count) {
// - move construct some T's at current end().
// - move assign some T's before current end().
// - destroy the old host for those "moved from" but not "moved to".
//
// This should leave a nice gap to construct the new range in without the need for move assigning via rotate afterwards.
//
// I don't know what to do about exception guarantees with that implementation though so I'll leave it to something to think
// about. Perhaps it can be used for T's with a non-throwing move assignment operator and move constructor.
// It will at least be ok for trivial types.
}
*/
public:
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto insert(const_iterator pos, const T& value) ->
typename std::enable_if<std::is_copy_constructible<U>::value, iterator>::type {
// static_assert(std::is_nothrow_move_assignable<T>::value, "only nothrow move assignable types may be used for now");
if (size() == capacity()) throw std::bad_alloc();
const auto ncpos = const_cast<iterator>(pos);
unchecked_push_back(value);
std::rotate(ncpos, std::prev(end()), end());
return ncpos;
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto insert(const_iterator pos, T&& value) ->
typename std::enable_if<std::is_move_constructible<U>::value, iterator>::type {
// static_assert(std::is_nothrow_move_assignable<T>::value, "only nothrow move assignable types may be used for now");
if (size() == capacity()) throw std::bad_alloc();
const auto ncpos = const_cast<iterator>(pos);
unchecked_push_back(std::move(value));
std::rotate(ncpos, std::prev(end()), end());
return ncpos;
}
template<class U = T>
LYNIPV_CXX20_CONSTEXPR auto insert(const_iterator pos, size_type count, const T& value) ->
typename std::enable_if<std::is_copy_constructible<U>::value, iterator>::type {
// static_assert(std::is_nothrow_move_assignable<T>::value, "only nothrow move assignable types may be used for now");
if (size() + count > capacity()) throw std::bad_alloc();
const auto ncpos = const_cast<iterator>(pos);
auto oldsize = size();
auto first_inserted = end();
try {
while (count--) {
unchecked_push_back(value);
}
}
catch (...) {
shrink_to(oldsize);
throw;
}
std::rotate(ncpos, first_inserted, end());
return ncpos;
}
template<class InputIt, class U = T>
LYNIPV_CXX20_CONSTEXPR auto insert(const_iterator pos, InputIt first, InputIt last) ->
typename std::enable_if<std::is_constructible<typename std::iterator_traits<InputIt>::value_type>::value &&
!std::is_const<U>::value,
iterator>::type {
// static_assert(std::is_nothrow_move_assignable<T>::value, "only nothrow move assignable types may be used for now");
const auto ncpos = const_cast<iterator>(pos);
auto oldsize = size();
auto first_inserted = end();
try {
for (; first != last; std::advance(first, 1)) {
push_back(*first);
}
}
catch (...) {
shrink_to(oldsize);
throw;
}
std::rotate(ncpos, first_inserted, end());
return ncpos;
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto insert(const_iterator pos, std::initializer_list<T> ilist) ->
typename std::enable_if<std::is_copy_constructible<U>::value && !std::is_const<U>::value, iterator>::type {
return insert(pos, ilist.begin(), ilist.end());
}
template<class... Args>
LYNIPV_CXX14_CONSTEXPR auto emplace(const_iterator pos, Args&&... args) ->
typename std::enable_if<std::is_constructible<T, Args...>::value, iterator>::type {
// static_assert(std::is_nothrow_move_assignable<T>::value, "only nothrow move assignable types may be used for now");
const auto ncpos = const_cast<iterator>(pos);
emplace_back(std::forward<Args>(args)...);
std::rotate(ncpos, std::prev(end()), end());
return ncpos;
}
template<class... Args>
LYNIPV_CXX14_CONSTEXPR auto unchecked_emplace_back(Args&&... args) ->
typename std::enable_if<std::is_constructible<T, Args...>::value, reference>::type {
auto& rv = construct(size(), std::forward<Args>(args)...);
this->inc();
return rv;
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto unchecked_push_back(T const& value) ->
typename std::enable_if<std::is_copy_constructible<U>::value, reference>::type {
auto& rv = construct(size(), value);
this->inc();
return rv;
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto unchecked_push_back(T&& value) ->
typename std::enable_if<std::is_move_constructible<U>::value, reference>::type {
auto& rv = construct(size(), std::move(value));
this->inc();
return rv;
}
template<class... Args>
LYNIPV_CXX14_CONSTEXPR auto emplace_back(Args&&... args) ->
typename std::enable_if<std::is_constructible<T, Args...>::value, reference>::type {
if (size() == N) throw std::bad_alloc();
return unchecked_emplace_back(std::forward<Args>(args)...);
}
template<class... Args>
LYNIPV_CXX14_CONSTEXPR auto try_emplace_back(Args&&... args) ->
typename std::enable_if<std::is_constructible<T, Args...>::value, pointer>::type {
if (size() == N) return nullptr;
return std::addressof(unchecked_emplace_back(std::forward<Args>(args)...));
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto push_back(T const& value) ->
typename std::enable_if<std::is_copy_constructible<U>::value, reference>::type {
if (size() == N) throw std::bad_alloc();
return unchecked_push_back(value);
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto push_back(T&& value) ->
typename std::enable_if<std::is_move_constructible<U>::value, reference>::type {
if (size() == N) throw std::bad_alloc();
return unchecked_push_back(std::move(value));
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto try_push_back(T const& value) ->
typename std::enable_if<std::is_copy_constructible<U>::value, pointer>::type {
if (size() == N) return nullptr;
return std::addressof(unchecked_push_back(value));
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto try_push_back(T&& value) ->
typename std::enable_if<std::is_move_constructible<U>::value, pointer>::type {
if (size() == N) return nullptr;
return std::addressof(unchecked_push_back(std::move(value)));
}
LYNIPV_CXX14_CONSTEXPR void pop_back() noexcept { destroy(this->dec()); }
LYNIPV_CXX14_CONSTEXPR void clear() noexcept { shrink_to(0); }
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto erase(const_iterator first, const_iterator last) ->
typename std::enable_if<!std::is_const<U>::value, iterator>::type {
auto ncfirst = const_cast<iterator>(first);
auto nclast = const_cast<iterator>(last);
auto removed = static_cast<std::size_t>(std::distance(ncfirst, nclast));
std::move(nclast, end(), ncfirst);
for (size_type idx = size() - removed; idx < size(); ++idx) {
destroy(idx);
}
this->dec(removed);
return ncfirst;
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto erase(const_iterator pos) -> typename std::enable_if<!std::is_const<U>::value, iterator>::type {
return erase(pos, std::next(pos));
}
template<class U = T>
LYNIPV_CXX14_CONSTEXPR auto swap(inplace_vector& other) noexcept(N == 0 ||
(lyn_inplace_vector_detail::is_nothrow_swappable<T>::value &&
std::is_nothrow_move_constructible<T>::value)) ->
typename std::enable_if<!std::is_const<U>::value>::type {
auto&& p = (size() < other.size()) ? std::pair<inplace_vector&, inplace_vector&>(*this, other)
: std::pair<inplace_vector&, inplace_vector&>(other, *this);
auto& small = p.first;
auto& large = p.second;
size_type idx = 0, small_size = small.size();
for (; idx < small_size; ++idx) {
using std::swap;
swap(small[idx], large[idx]);
}
for (; idx < large.size(); ++idx) {
small.push_back(std::move(large[idx]));
}
large.shrink_to(small_size);
}
LYNIPV_CXX14_CONSTEXPR void friend swap(inplace_vector& lhs, inplace_vector& rhs) noexcept(
N == 0 || (lyn_inplace_vector_detail::is_nothrow_swappable<T>::value && std::is_nothrow_move_constructible<T>::value)) {
lhs.swap(rhs);
}
#if __cplusplus >= 202002L
constexpr friend auto operator<=>(const inplace_vector& lhs, const inplace_vector& rhs) {
return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
#else
friend bool operator<(const inplace_vector& lhs, const inplace_vector& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
friend bool operator>(const inplace_vector& lhs, const inplace_vector& rhs) { return rhs < lhs; }
friend bool operator<=(const inplace_vector& lhs, const inplace_vector& rhs) { return !(rhs < lhs); }
friend bool operator>=(const inplace_vector& lhs, const inplace_vector& rhs) { return rhs <= lhs; }
friend bool operator!=(const inplace_vector& lhs, const inplace_vector& rhs) { return !(lhs == rhs); }
#endif
friend bool operator==(const inplace_vector& lhs, const inplace_vector& rhs) {
if (lhs.size() != rhs.size()) return false;
return std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin());
}
};
template<class T, size_t N, class U = T>
LYNIPV_CXX14_CONSTEXPR typename inplace_vector<T, N>::size_type erase(inplace_vector<T, N>& c, const U& value) {
auto it = std::remove(c.begin(), c.end(), value);
auto r = static_cast<typename inplace_vector<T, N>::size_type>(std::distance(it, c.end()));
c.erase(it, it.end());
return r;
}
template<class T, size_t N, class Predicate>
LYNIPV_CXX14_CONSTEXPR typename inplace_vector<T, N>::size_type erase_if(inplace_vector<T, N>& c, Predicate pred) {
auto it = std::remove_if(c.begin(), c.end(), pred);
auto r = static_cast<typename inplace_vector<T, N>::size_type>(std::distance(it, c.end()));
c.erase(it, c.end());
return r;
}
} // namespace lyn
// clean up defines
#undef LYNIPV_CXX14_CONSTEXPR
#undef LYNIPV_CXX20_CONSTEXPR
#undef LYNIPV_CONSTRUCT_AT
#endif

198
include/Util/RandomPicker.h Normal file
View File

@@ -0,0 +1,198 @@
#pragma once
#include <memory>
#include <type_traits>
template <typename T, typename WeightType>
class RandomPickerT
{
public:
struct Entry
{
Entry() = default;
Entry(const T& val, WeightType weight) : Val{ val }, Weight{ weight } {}
T Val{};
const WeightType Weight{};
WeightType AccumulatedWeight{};
WeightType GetWeightSum() { return AccumulatedWeight + Weight; }
};
public:
std::unique_ptr<Entry[]> Entries{};
WeightType TotalWeight{};
uint32_t TotalEntries{};
const uint32_t DataSize{};
public:
RandomPickerT() = default;
template <typename Container, typename WeightGetter>
RandomPickerT(const Container& container, WeightGetter getter)
: Entries{ std::make_unique<Entry[]>(container.size()) }
, TotalEntries{ container.size() }
, DataSize{ container.size() }
{
int counter{};
for (const auto& entry : container)
{
auto weight = getter(entry);
Entries[counter++] = Entry{entry, weight};
}
RecalculateWeights();
}
template <typename EntriesT>
RandomPickerT(const EntriesT& entries)
: Entries{ std::make_unique<Entry[]>(entries.size()) }
, TotalEntries{ entries.size() }
, DataSize{ entries.size() }
{
int counter{};
for (const auto& entry : entries)
{
Entries[counter++] = entry;
}
RecalculateWeights();
}
public:
T GetRandom(int& index, WeightType randomVal) const
{
WeightType weight = GetRandomWeight(randomVal);
index = GetIndex(weight);
return Entries[index].Val;
}
int GetIndex(WeightType weight) const
{
if (weight < 0 || weight > TotalWeight || TotalEntries == 0)
return -1;
if (TotalEntries == 1)
return 0;
return BinarySearchRecursive(weight, 0, TotalEntries - 1);
}
void RemoveEntry(int index)
{
if (ValidateIndex(index)) RemoveEntryInternal(index);
}
T GetAndRemoveRandom(int& index, WeightType randomVal)
{
WeightType weight = GetRandomWeight(randomVal);
int index = GetIndex(weight);
auto returnVal = Entries[index].Val;
RemoveEntryInternal(index);
return returnVal;
}
T GetAndRemoveRandom(WeightType randomVal)
{
int index{};
return GetAndRemoveRandom(index, randomVal);
}
T Peek(int index) const
{
_ASSERT(ValidateIndex(index));
return Entries[index];
}
void Reset()
{
TotalEntries = DataSize;
RecalculateWeights();
}
template <typename WeightGetter>
void SetObjectAtIndex(int index, const T& val, WeightGetter getter)
{
if (!ValidateIndex(index)) return;
auto entry = Entries[index];
auto newWeight = getter(val);
bool recalculateWeights = newWeight != entry.Weight;
Entries[index] = Entry{ val, newWeight };
if (recalculateWeights) RecalculateWeights();
}
void SetEntryAtIndex(int index, const Entry& entry)
{
if (!ValidateIndex(index)) return;
bool recalculateWeights = entry.Weight != Entries[index].Weight;
Entries[index] = entry;
if (recalculateWeights) RecalculateWeights();
}
private:
void RecalculateWeights()
{
WeightType accumulatedWeight = 0;
for (uint32_t i{}; i < TotalEntries; ++i)
{
Entries[i].AccumulatedWeight = accumulatedWeight;
accumulatedWeight += Entries[i].Weight;
}
TotalWeight = accumulatedWeight;
}
WeightType GetRandomWeight(WeightType randomVal) const
{
if constexpr (std::is_integral_v<WeightType>)
{
return randomVal % TotalWeight;
}
if constexpr (std::is_floating_point_v<WeightType>)
{
return std::fmod(randomVal, TotalWeight);
}
}
int BinarySearchRecursive(WeightType weight, int min, int max) const
{
int middle = (min + max) >> 1;
auto entry = Entries[middle];
if (weight >= entry.AccumulatedWeight && weight < entry.GetWeightSum())
return middle;
return (weight < entry.AccumulatedWeight) ? BinarySearchRecursive(weight, min, middle - 1) : BinarySearchRecursive(weight, middle + 1, max);
}
void RemoveEntryInternal(int index)
{
if (!ValidateIndex(index))
return;
std::swap(Entries[index], Entries.back());
--TotalEntries;
if (TotalEntries != 0) RecalculateWeights();
else TotalWeight = 0;
}
bool ValidateIndex(int index) const
{
return index >= 0 && index < TotalEntries;
}
};
template <typename T>
using RandomPicker32 = RandomPickerT<T, uint32_t>;
template <typename T>
using RandomPicker64 = RandomPickerT<T, uint64_t>;
template <typename T>
using RandomPickerF = RandomPickerT<T, float>;
template <typename T>
using RandomPickerD = RandomPickerT<T, double>;

View File

@@ -0,0 +1,41 @@
#pragma once
#include "core/os/mutex.h"
#include "core/object/ref_counted.h"
template <typename T, typename LockT>
class ResourceAccess final
{
public:
ResourceAccess(T& resource, LockT& lock)
: Resource{ resource }
, Lock{ lock }
{
Lock.lock();
}
~ResourceAccess()
{
if (validLock) Lock.unlock();
}
ResourceAccess(const ResourceAccess& other) = delete;
ResourceAccess& operator=(const ResourceAccess& other) = delete;
ResourceAccess& operator=(ResourceAccess&& other) noexcept = delete;
ResourceAccess(ResourceAccess&& other) noexcept
: Resource{ other.Resource }
, Lock{ other.Lock }
, validLock{ true }
{
other.validLock = false;
}
public:
T& GetResource() { return Resource; }
T* operator->() { return &Resource; }
private:
T& Resource;
LockT& Lock;
bool validLock{ true };
};

113
include/Util/SharedBuffer.h Normal file
View File

@@ -0,0 +1,113 @@
#pragma once
#include "stdint.h"
#include "cassert"
#include <atomic>
#include "Util/Span.h"
template <typename T>
inline T const* ByteToData(uint8_t const* data) { return reinterpret_cast<T const*>(data); }
template <typename T>
inline T* ByteToData(uint8_t* data) { return reinterpret_cast<T*>(data); }
template <typename T, typename MetaData>
class SharedBuffer final
{
private:
struct ControlBlock
{
ControlBlock(uint32_t size, const MetaData& data) : Size{ size }, Data { data } {}
std::atomic<uint32_t> Count{ 1 };
uint32_t Size{};
MetaData Data{};
};
static constexpr size_t ControlBlockOffset = 0;
static constexpr size_t DataOffset = sizeof(ControlBlock);
public:
SharedBuffer() = default;
SharedBuffer(int size, const MetaData& metaData)
{
Data = new uint8_t[sizeof(ControlBlock) + size * sizeof(T)]{};
new (GetControlBlock()) ControlBlock(size, metaData);
for (size_t i{}; i < size; ++i)
{
new (&Ptr()[i]) T{};
}
}
~SharedBuffer()
{
Destruct();
}
SharedBuffer(const SharedBuffer& other)
: Data{ other.Data }
{
if (Data) ++GetControlBlock()->Count;
}
SharedBuffer(SharedBuffer&& other) noexcept
: Data{ other.Data }
{
other.Data = nullptr;
}
SharedBuffer& operator=(const SharedBuffer& other)
{
if (this != &other)
{
Destruct();
Data = other.Data;
if (Data) ++GetControlBlock()->Count;
}
return *this;
}
SharedBuffer& operator=(SharedBuffer&& other) noexcept
{
if (this != &other)
{
Destruct();
Data = other.Data;
other.Data = nullptr;
}
return *this;
}
private:
ControlBlock const* GetControlBlock() const { assert(Data); return ByteToData<ControlBlock>(Data + ControlBlockOffset); }
ControlBlock* GetControlBlock() { assert(Data); return ByteToData<ControlBlock>(Data + ControlBlockOffset); }
void Destruct()
{
if (Data != nullptr && --GetControlBlock()->Count == 0)
{
uint32_t size = GetSize();
GetControlBlock()->~ControlBlock();
for (size_t i = 0; i < size; ++i)
Ptr()[i].~T();
delete[] Data;
}
}
public:
T const* Ptr() const { assert(Data); return ByteToData<T>(Data + DataOffset); }
T* Ptr() { assert(Data); return ByteToData<T>(Data + DataOffset); }
tcb::span<const T> GetData() const { return tcb::span<const T>(Ptr(), GetSize()); }
tcb::span<T> GetData() { return tcb::span<T>(Ptr(), GetSize()); }
MetaData const* GetMetaData() const { assert(Data); return &GetControlBlock()->Data; }
MetaData* GetMetaData() { assert(Data); return &GetControlBlock()->Data; }
T& operator[] (uint32_t index) { assert(index < GetControlBlock()->Size); return GetData()[index]; }
const T& operator[] (uint32_t index) const { assert(index < GetControlBlock()->Size); return GetData()[index]; }
uint32_t GetSize() const { return GetControlBlock()->Size; }
operator bool() const { return Data; }
private:
uint8_t* Data{};
};

630
include/Util/Span.h Normal file
View File

@@ -0,0 +1,630 @@
/*
This is an implementation of C++20's std::span
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/n4820.pdf
*/
// Copyright Tristan Brindle 2018.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file ../../LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
#ifndef TCB_SPAN_HPP_INCLUDED
#define TCB_SPAN_HPP_INCLUDED
#include <array>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#ifndef TCB_SPAN_NO_EXCEPTIONS
// Attempt to discover whether we're being compiled with exception support
#if !(defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
#define TCB_SPAN_NO_EXCEPTIONS
#endif
#endif
#ifndef TCB_SPAN_NO_EXCEPTIONS
#include <cstdio>
#include <stdexcept>
#endif
// Various feature test macros
#ifndef TCB_SPAN_NAMESPACE_NAME
#define TCB_SPAN_NAMESPACE_NAME tcb
#endif
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
#define TCB_SPAN_HAVE_CPP17
#endif
#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
#define TCB_SPAN_HAVE_CPP14
#endif
namespace TCB_SPAN_NAMESPACE_NAME {
// Establish default contract checking behavior
#if !defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) && \
!defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) && \
!defined(TCB_SPAN_NO_CONTRACT_CHECKING)
#if defined(NDEBUG) || !defined(TCB_SPAN_HAVE_CPP14)
#define TCB_SPAN_NO_CONTRACT_CHECKING
#else
#define TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION
#endif
#endif
#if defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION)
struct contract_violation_error : std::logic_error {
explicit contract_violation_error(const char* msg) : std::logic_error(msg)
{
}
};
inline void contract_violation(const char* msg)
{
throw contract_violation_error(msg);
}
#elif defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION)
[[noreturn]] inline void contract_violation(const char* /*unused*/)
{
std::terminate();
}
#endif
#if !defined(TCB_SPAN_NO_CONTRACT_CHECKING)
#define TCB_SPAN_STRINGIFY(cond) #cond
#define TCB_SPAN_EXPECT(cond) \
cond ? (void) 0 : contract_violation("Expected " TCB_SPAN_STRINGIFY(cond))
#else
#define TCB_SPAN_EXPECT(cond)
#endif
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_inline_variables)
#define TCB_SPAN_INLINE_VAR inline
#else
#define TCB_SPAN_INLINE_VAR
#endif
#if defined(TCB_SPAN_HAVE_CPP14) || \
(defined(__cpp_constexpr) && __cpp_constexpr >= 201304)
#define TCB_SPAN_HAVE_CPP14_CONSTEXPR
#endif
#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR)
#define TCB_SPAN_CONSTEXPR14 constexpr
#else
#define TCB_SPAN_CONSTEXPR14
#endif
#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) && \
(!defined(_MSC_VER) || _MSC_VER > 1900)
#define TCB_SPAN_CONSTEXPR_ASSIGN constexpr
#else
#define TCB_SPAN_CONSTEXPR_ASSIGN
#endif
#if defined(TCB_SPAN_NO_CONTRACT_CHECKING)
#define TCB_SPAN_CONSTEXPR11 constexpr
#else
#define TCB_SPAN_CONSTEXPR11 TCB_SPAN_CONSTEXPR14
#endif
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_deduction_guides)
#define TCB_SPAN_HAVE_DEDUCTION_GUIDES
#endif
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_byte)
#define TCB_SPAN_HAVE_STD_BYTE
#endif
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_array_constexpr)
#define TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC
#endif
#if defined(TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC)
#define TCB_SPAN_ARRAY_CONSTEXPR constexpr
#else
#define TCB_SPAN_ARRAY_CONSTEXPR
#endif
#ifdef TCB_SPAN_HAVE_STD_BYTE
using byte = std::byte;
#else
using byte = unsigned char;
#endif
#if defined(TCB_SPAN_HAVE_CPP17)
#define TCB_SPAN_NODISCARD [[nodiscard]]
#else
#define TCB_SPAN_NODISCARD
#endif
TCB_SPAN_INLINE_VAR constexpr std::size_t dynamic_extent = SIZE_MAX;
template <typename ElementType, std::size_t Extent = dynamic_extent>
class span;
namespace detail {
template <typename E, std::size_t S>
struct span_storage {
constexpr span_storage() noexcept = default;
constexpr span_storage(E* p_ptr, std::size_t /*unused*/) noexcept
: ptr(p_ptr)
{
}
E* ptr = nullptr;
static constexpr std::size_t size = S;
};
template <typename E>
struct span_storage<E, dynamic_extent> {
constexpr span_storage() noexcept = default;
constexpr span_storage(E* p_ptr, std::size_t p_size) noexcept
: ptr(p_ptr), size(p_size)
{
}
E* ptr = nullptr;
std::size_t size = 0;
};
// Reimplementation of C++17 std::size() and std::data()
#if defined(TCB_SPAN_HAVE_CPP17) || \
defined(__cpp_lib_nonmember_container_access)
using std::data;
using std::size;
#else
template <class C>
constexpr auto size(const C& c) -> decltype(c.size())
{
return c.size();
}
template <class T, std::size_t N>
constexpr std::size_t size(const T(&)[N]) noexcept
{
return N;
}
template <class C>
constexpr auto data(C& c) -> decltype(c.data())
{
return c.data();
}
template <class C>
constexpr auto data(const C& c) -> decltype(c.data())
{
return c.data();
}
template <class T, std::size_t N>
constexpr T* data(T(&array)[N]) noexcept
{
return array;
}
template <class E>
constexpr const E* data(std::initializer_list<E> il) noexcept
{
return il.begin();
}
#endif // TCB_SPAN_HAVE_CPP17
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_void_t)
using std::void_t;
#else
template <typename...>
using void_t = void;
#endif
template <typename T>
using uncvref_t =
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template <typename>
struct is_span : std::false_type {};
template <typename T, std::size_t S>
struct is_span<span<T, S>> : std::true_type {};
template <typename>
struct is_std_array : std::false_type {};
template <typename T, std::size_t N>
struct is_std_array<std::array<T, N>> : std::true_type {};
template <typename, typename = void>
struct has_size_and_data : std::false_type {};
template <typename T>
struct has_size_and_data<T, void_t<decltype(detail::size(std::declval<T>())),
decltype(detail::data(std::declval<T>()))>>
: std::true_type {};
template <typename C, typename U = uncvref_t<C>>
struct is_container {
static constexpr bool value =
!is_span<U>::value && !is_std_array<U>::value &&
!std::is_array<U>::value && has_size_and_data<C>::value;
};
template <typename T>
using remove_pointer_t = typename std::remove_pointer<T>::type;
template <typename, typename, typename = void>
struct is_container_element_type_compatible : std::false_type {};
template <typename T, typename E>
struct is_container_element_type_compatible<
T, E,
typename std::enable_if<
!std::is_same<
typename std::remove_cv<decltype(detail::data(std::declval<T>()))>::type,
void>::value&&
std::is_convertible<
remove_pointer_t<decltype(detail::data(std::declval<T>()))>(*)[],
E(*)[]>::value
>::type>
: std::true_type {
};
template <typename, typename = size_t>
struct is_complete : std::false_type {};
template <typename T>
struct is_complete<T, decltype(sizeof(T))> : std::true_type {};
} // namespace detail
template <typename ElementType, std::size_t Extent>
class span {
static_assert(std::is_object<ElementType>::value,
"A span's ElementType must be an object type (not a "
"reference type or void)");
static_assert(detail::is_complete<ElementType>::value,
"A span's ElementType must be a complete type (not a forward "
"declaration)");
static_assert(!std::is_abstract<ElementType>::value,
"A span's ElementType cannot be an abstract class type");
using storage_type = detail::span_storage<ElementType, Extent>;
public:
// constants and types
using element_type = ElementType;
using value_type = typename std::remove_cv<ElementType>::type;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = element_type*;
using const_pointer = const element_type*;
using reference = element_type&;
using const_reference = const element_type&;
using iterator = pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
static constexpr size_type extent = Extent;
// [span.cons], span constructors, copy, assignment, and destructor
template <
std::size_t E = Extent,
typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0>
constexpr span() noexcept
{
}
TCB_SPAN_CONSTEXPR11 span(pointer ptr, size_type count)
: storage_(ptr, count)
{
TCB_SPAN_EXPECT(extent == dynamic_extent || count == extent);
}
TCB_SPAN_CONSTEXPR11 span(pointer first_elem, pointer last_elem)
: storage_(first_elem, last_elem - first_elem)
{
TCB_SPAN_EXPECT(extent == dynamic_extent ||
last_elem - first_elem ==
static_cast<std::ptrdiff_t>(extent));
}
template <std::size_t N, std::size_t E = Extent,
typename std::enable_if<
(E == dynamic_extent || N == E) &&
detail::is_container_element_type_compatible<
element_type(&)[N], ElementType>::value,
int>::type = 0>
constexpr span(element_type(&arr)[N]) noexcept : storage_(arr, N)
{
}
template <typename T, std::size_t N, std::size_t E = Extent,
typename std::enable_if<
(E == dynamic_extent || N == E) &&
detail::is_container_element_type_compatible<
std::array<T, N>&, ElementType>::value,
int>::type = 0>
TCB_SPAN_ARRAY_CONSTEXPR span(std::array<T, N>& arr) noexcept
: storage_(arr.data(), N)
{
}
template <typename T, std::size_t N, std::size_t E = Extent,
typename std::enable_if<
(E == dynamic_extent || N == E) &&
detail::is_container_element_type_compatible<
const std::array<T, N>&, ElementType>::value,
int>::type = 0>
TCB_SPAN_ARRAY_CONSTEXPR span(const std::array<T, N>& arr) noexcept
: storage_(arr.data(), N)
{
}
template <
typename Container, std::size_t E = Extent,
typename std::enable_if<
E == dynamic_extent && detail::is_container<Container>::value&&
detail::is_container_element_type_compatible<
Container&, ElementType>::value,
int>::type = 0>
constexpr span(Container & cont)
: storage_(detail::data(cont), detail::size(cont))
{
}
template <
typename Container, std::size_t E = Extent,
typename std::enable_if<
E == dynamic_extent && detail::is_container<Container>::value&&
detail::is_container_element_type_compatible<
const Container&, ElementType>::value,
int>::type = 0>
constexpr span(const Container & cont)
: storage_(detail::data(cont), detail::size(cont))
{
}
constexpr span(const span& other) noexcept = default;
template <typename OtherElementType, std::size_t OtherExtent,
typename std::enable_if<
(Extent == dynamic_extent || OtherExtent == dynamic_extent ||
Extent == OtherExtent) &&
std::is_convertible<OtherElementType(*)[],
ElementType(*)[]>::value,
int>::type = 0>
constexpr span(const span<OtherElementType, OtherExtent>& other) noexcept
: storage_(other.data(), other.size())
{
}
~span() noexcept = default;
TCB_SPAN_CONSTEXPR_ASSIGN span&
operator=(const span& other) noexcept = default;
// [span.sub], span subviews
template <std::size_t Count>
TCB_SPAN_CONSTEXPR11 span<element_type, Count> first() const
{
TCB_SPAN_EXPECT(Count <= size());
return { data(), Count };
}
template <std::size_t Count>
TCB_SPAN_CONSTEXPR11 span<element_type, Count> last() const
{
TCB_SPAN_EXPECT(Count <= size());
return { data() + (size() - Count), Count };
}
template <std::size_t Offset, std::size_t Count = dynamic_extent>
using subspan_return_t =
span<ElementType, Count != dynamic_extent
? Count
: (Extent != dynamic_extent ? Extent - Offset
: dynamic_extent)>;
template <std::size_t Offset, std::size_t Count = dynamic_extent>
TCB_SPAN_CONSTEXPR11 subspan_return_t<Offset, Count> subspan() const
{
TCB_SPAN_EXPECT(Offset <= size() &&
(Count == dynamic_extent || Offset + Count <= size()));
return { data() + Offset,
Count != dynamic_extent ? Count : size() - Offset };
}
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
first(size_type count) const
{
TCB_SPAN_EXPECT(count <= size());
return { data(), count };
}
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
last(size_type count) const
{
TCB_SPAN_EXPECT(count <= size());
return { data() + (size() - count), count };
}
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
subspan(size_type offset, size_type count = dynamic_extent) const
{
TCB_SPAN_EXPECT(offset <= size() &&
(count == dynamic_extent || offset + count <= size()));
return { data() + offset,
count == dynamic_extent ? size() - offset : count };
}
// [span.obs], span observers
constexpr size_type size() const noexcept { return storage_.size; }
constexpr size_type size_bytes() const noexcept
{
return size() * sizeof(element_type);
}
TCB_SPAN_NODISCARD constexpr bool empty() const noexcept
{
return size() == 0;
}
// [span.elem], span element access
TCB_SPAN_CONSTEXPR11 reference operator[](size_type idx) const
{
TCB_SPAN_EXPECT(idx < size());
return *(data() + idx);
}
TCB_SPAN_CONSTEXPR11 reference front() const
{
TCB_SPAN_EXPECT(!empty());
return *data();
}
TCB_SPAN_CONSTEXPR11 reference back() const
{
TCB_SPAN_EXPECT(!empty());
return *(data() + (size() - 1));
}
constexpr pointer data() const noexcept { return storage_.ptr; }
// [span.iterators], span iterator support
constexpr iterator begin() const noexcept { return data(); }
constexpr iterator end() const noexcept { return data() + size(); }
TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rbegin() const noexcept
{
return reverse_iterator(end());
}
TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rend() const noexcept
{
return reverse_iterator(begin());
}
private:
storage_type storage_{};
};
#ifdef TCB_SPAN_HAVE_DEDUCTION_GUIDES
/* Deduction Guides */
template <class T, size_t N>
span(T(&)[N]) -> span<T, N>;
template <class T, size_t N>
span(std::array<T, N>&) -> span<T, N>;
template <class T, size_t N>
span(const std::array<T, N>&) -> span<const T, N>;
template <class Container>
span(Container&) -> span<typename std::remove_reference<
decltype(*detail::data(std::declval<Container&>()))>::type>;
template <class Container>
span(const Container&) -> span<const typename Container::value_type>;
#endif // TCB_HAVE_DEDUCTION_GUIDES
template <typename ElementType, std::size_t Extent>
constexpr span<ElementType, Extent>
make_span(span<ElementType, Extent> s) noexcept
{
return s;
}
template <typename T, std::size_t N>
constexpr span<T, N> make_span(T(&arr)[N]) noexcept
{
return { arr };
}
template <typename T, std::size_t N>
TCB_SPAN_ARRAY_CONSTEXPR span<T, N> make_span(std::array<T, N>& arr) noexcept
{
return { arr };
}
template <typename T, std::size_t N>
TCB_SPAN_ARRAY_CONSTEXPR span<const T, N>
make_span(const std::array<T, N>& arr) noexcept
{
return { arr };
}
template <typename Container>
constexpr span<typename std::remove_reference<
decltype(*detail::data(std::declval<Container&>()))>::type>
make_span(Container& cont)
{
return { cont };
}
template <typename Container>
constexpr span<const typename Container::value_type>
make_span(const Container& cont)
{
return { cont };
}
template <typename ElementType, std::size_t Extent>
span<const byte, ((Extent == dynamic_extent) ? dynamic_extent
: sizeof(ElementType) * Extent)>
as_bytes(span<ElementType, Extent> s) noexcept
{
return { reinterpret_cast<const byte*>(s.data()), s.size_bytes() };
}
template <
class ElementType, size_t Extent,
typename std::enable_if<!std::is_const<ElementType>::value, int>::type = 0>
span<byte, ((Extent == dynamic_extent) ? dynamic_extent
: sizeof(ElementType) * Extent)>
as_writable_bytes(span<ElementType, Extent> s) noexcept
{
return { reinterpret_cast<byte*>(s.data()), s.size_bytes() };
}
template <std::size_t N, typename E, std::size_t S>
constexpr auto get(span<E, S> s) -> decltype(s[N])
{
return s[N];
}
} // namespace TCB_SPAN_NAMESPACE_NAME
namespace std {
template <typename ElementType, size_t Extent>
class tuple_size<TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>>
: public integral_constant<size_t, Extent> {
};
template <typename ElementType>
class tuple_size<TCB_SPAN_NAMESPACE_NAME::span<
ElementType, TCB_SPAN_NAMESPACE_NAME::dynamic_extent>>; // not defined
template <size_t I, typename ElementType, size_t Extent>
class tuple_element<I, TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>> {
public:
static_assert(Extent != TCB_SPAN_NAMESPACE_NAME::dynamic_extent &&
I < Extent,
"");
using type = ElementType;
};
} // end namespace std
#endif // TCB_SPAN_HPP_INCLUDED

View File

@@ -0,0 +1,53 @@
#pragma once
#include <iostream>
#include <cassert>
#include "core/error/error_macros.h"
class StackAllocator {
public:
StackAllocator() noexcept = default;
StackAllocator(size_t size) noexcept
{
m_memory = new char[size];
m_size = size;
m_offset = 0;
}
~StackAllocator()
{
delete[] m_memory;
}
template <typename T>
T* allocate(size_t size = 1)
{
return static_cast<T*>(allocate(size * sizeof(T)));
}
void* allocate(size_t size = 1)
{
size = std::max<size_t>(size, 8);
if (m_offset + size > m_size)
{
throw std::bad_alloc();
}
void* ptr = m_memory + m_offset;
m_offset += size;
return ptr;
}
template <typename T>
void deallocate(T* p, size_t size) noexcept
{}
void reset() {
m_offset = 0;
}
private:
char* m_memory;
size_t m_size;
size_t m_offset;
};

252
include/Util/fsa.h Normal file
View File

@@ -0,0 +1,252 @@
/*
A* Algorithm Implementation using STL is
Copyright (C)2001-2005 Justin Heyes-Jones
Permission is given by the author to freely redistribute and
include this code in any program as long as this credit is
given where due.
COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE
IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED
CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL
DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY
NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE
OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
THIS DISCLAIMER.
Use at your own risk!
FixedSizeAllocator class
Copyright 2001 Justin Heyes-Jones
This class is a constant time O(1) memory manager for objects of
a specified type. The type is specified using a template class.
Memory is allocated from a fixed size buffer which you can specify in the
class constructor or use the default.
Using GetFirst and GetNext it is possible to iterate through the elements
one by one, and this would be the most common use for the class.
I would suggest using this class when you want O(1) add and delete
and you don't do much searching, which would be O(n). Structures such as binary
trees can be used instead to get O(logn) access time.
*/
#ifndef FSA_H
#define FSA_H
#include <string.h>
#include <stdio.h>
template <class USER_TYPE> class FixedSizeAllocator
{
public:
// Constants
enum
{
FSA_DEFAULT_SIZE = 100
};
// This class enables us to transparently manage the extra data
// needed to enable the user class to form part of the double-linked
// list class
struct FSA_ELEMENT
{
USER_TYPE UserType;
FSA_ELEMENT* pPrev;
FSA_ELEMENT* pNext;
};
public: // methods
FixedSizeAllocator(unsigned int MaxElements = FSA_DEFAULT_SIZE) :
m_pFirstUsed(NULL),
m_MaxElements(MaxElements)
{
// Allocate enough memory for the maximum number of elements
char* pMem = new char[m_MaxElements * sizeof(FSA_ELEMENT)];
m_pMemory = (FSA_ELEMENT*)pMem;
// Set the free list first pointer
m_pFirstFree = m_pMemory;
// Clear the memory
memset(m_pMemory, 0, sizeof(FSA_ELEMENT) * m_MaxElements);
// Point at first element
FSA_ELEMENT* pElement = m_pFirstFree;
// Set the double linked free list
for (unsigned int i = 0; i < m_MaxElements; i++)
{
pElement->pPrev = pElement - 1;
pElement->pNext = pElement + 1;
pElement++;
}
// first element should have a null prev
m_pFirstFree->pPrev = NULL;
// last element should have a null next
(pElement - 1)->pNext = NULL;
}
~FixedSizeAllocator()
{
// Free up the memory
delete[](char*) m_pMemory;
}
// Allocate a new USER_TYPE and return a pointer to it
USER_TYPE* alloc()
{
FSA_ELEMENT* pNewNode = NULL;
if (!m_pFirstFree)
{
return NULL;
}
else
{
pNewNode = m_pFirstFree;
m_pFirstFree = pNewNode->pNext;
// if the new node points to another free node then
// change that nodes prev free pointer...
if (pNewNode->pNext)
{
pNewNode->pNext->pPrev = NULL;
}
// node is now on the used list
pNewNode->pPrev = NULL; // the allocated node is always first in the list
if (m_pFirstUsed == NULL)
{
pNewNode->pNext = NULL; // no other nodes
}
else
{
m_pFirstUsed->pPrev = pNewNode; // insert this at the head of the used list
pNewNode->pNext = m_pFirstUsed;
}
m_pFirstUsed = pNewNode;
}
return reinterpret_cast<USER_TYPE*>(pNewNode);
}
// Free the given user type
// For efficiency I don't check whether the user_data is a valid
// pointer that was allocated. I may add some debug only checking
// (To add the debug check you'd need to make sure the pointer is in
// the m_pMemory area and is pointing at the start of a node)
void free(USER_TYPE* user_data)
{
FSA_ELEMENT* pNode = reinterpret_cast<FSA_ELEMENT*>(user_data);
// manage used list, remove this node from it
if (pNode->pPrev)
{
pNode->pPrev->pNext = pNode->pNext;
}
else
{
// this handles the case that we delete the first node in the used list
m_pFirstUsed = pNode->pNext;
}
if (pNode->pNext)
{
pNode->pNext->pPrev = pNode->pPrev;
}
// add to free list
if (m_pFirstFree == NULL)
{
// free list was empty
m_pFirstFree = pNode;
pNode->pPrev = NULL;
pNode->pNext = NULL;
}
else
{
// Add this node at the start of the free list
m_pFirstFree->pPrev = pNode;
pNode->pNext = m_pFirstFree;
m_pFirstFree = pNode;
}
}
// For debugging this displays both lists (using the prev/next list pointers)
void Debug()
{
printf("free list ");
FSA_ELEMENT* p = m_pFirstFree;
while (p)
{
printf("%x!%x ", p->pPrev, p->pNext);
p = p->pNext;
}
printf("\n");
printf("used list ");
p = m_pFirstUsed;
while (p)
{
printf("%x!%x ", p->pPrev, p->pNext);
p = p->pNext;
}
printf("\n");
}
// Iterators
USER_TYPE* GetFirst()
{
return reinterpret_cast<USER_TYPE*>(m_pFirstUsed);
}
USER_TYPE* GetNext(USER_TYPE* node)
{
return reinterpret_cast<USER_TYPE*>
(
(reinterpret_cast<FSA_ELEMENT*>(node))->pNext
);
}
public: // data
private: // methods
private: // data
FSA_ELEMENT* m_pFirstFree;
FSA_ELEMENT* m_pFirstUsed;
unsigned int m_MaxElements;
FSA_ELEMENT* m_pMemory;
};
#endif // defined FSA_H

21
include/config.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <stdint.h>
#include <limits>
#include <cassert>
typedef uint16_t UnderlyingItemT;
constexpr uint32_t framesPerSecond = 32;
inline float FrameToSeconds(uint32_t frame)
{
return static_cast<float>(frame) / framesPerSecond;
}
inline uint32_t SecondsToFrames(float time)
{
return static_cast<uint32_t>(time * framesPerSecond);
}
#define DEV_ASSERT(x) assert(x)

View File

@@ -1 +0,0 @@
add_subdirectory(core)

View File

@@ -0,0 +1,15 @@
#include "Components/Configs/WorldConfig.hpp"
uint16_t WorldConfig::RegisterItem(const std::string& name)
{
for (uint16_t i = 0; i < Items.size(); ++i)
{
if (Items[i].Name == name) return i;
}
ItemConfig cfg{};
cfg.Name = name;
Items.push_back(std::move(cfg));
return static_cast<uint16_t>(Items.size() - 1);
}

186
src/Components/Support.cpp Normal file
View File

@@ -0,0 +1,186 @@
#include "Components/Support.h"
#include <queue>
#include <algorithm>
namespace
{
struct SupportNode
{
flecs::entity Entity;
Vector2 Pos;
};
bool InSpan(tcb::span<const Vector2> span, Vector2 pos)
{
return std::any_of(span.begin(), span.end(), [&](const Vector2& p) {
return p.X == pos.X && p.Y == pos.Y;
});
}
} // namespace
flecs::entity GetSupport(flecs::world& world, Vector2 pos)
{
flecs::entity result{};
world.each([&](flecs::entity e, const TilePosition& tp, const Support&) {
if (tp.Position.X == pos.X && tp.Position.Y == pos.Y)
result = e;
});
return result;
}
void RecalculateSupport(flecs::world& world,
tcb::span<const Vector2> skip,
tcb::span<const Vector2> unground)
{
// Collect all support nodes first (avoid nested world.each() re-entrancy)
std::vector<SupportNode> nodes;
world.each([&](flecs::entity e, const TilePosition& tp, const Support&) {
if (!InSpan(skip, tp.Position))
nodes.push_back({ e, tp.Position });
});
// Reset all SupportsAvailable to 0
for (auto& node : nodes)
node.Entity.ensure<Support>().SupportsAvailable = 0;
// Seed queue with grounded supports
std::queue<SupportNode> queue;
for (auto& node : nodes)
{
if (node.Entity.has<GroundedSupport>() && !InSpan(unground, node.Pos))
{
uint8_t maxSupport = node.Entity.get<Support>().MaxSupport;
node.Entity.ensure<Support>().SupportsAvailable = maxSupport;
queue.push(node);
}
}
// BFS: propagate support outward from grounded anchors
const Vector2 offsets[] = { {0, 1}, {0, -1}, {1, 0}, {-1, 0} };
while (!queue.empty())
{
SupportNode current = queue.front();
queue.pop();
uint8_t currentAvailable = current.Entity.get<Support>().SupportsAvailable;
for (const auto& offset : offsets)
{
uint8_t cost = (offset.X != 0) ? 2 : 1;
if (currentAvailable < cost) continue;
Vector2 neighborPos = current.Pos + offset;
auto it = std::find_if(nodes.begin(), nodes.end(), [&](const SupportNode& n) {
return n.Pos.X == neighborPos.X && n.Pos.Y == neighborPos.Y;
});
if (it == nodes.end()) continue;
uint8_t neighborMax = it->Entity.get<Support>().MaxSupport;
uint8_t candidate = std::min(static_cast<uint8_t>(currentAvailable - cost), neighborMax);
uint8_t neighborCurrent = it->Entity.get<Support>().SupportsAvailable;
if (candidate > neighborCurrent)
{
it->Entity.ensure<Support>().SupportsAvailable = candidate;
queue.push(*it);
}
}
}
}
bool CanRemove(flecs::world& world, tcb::span<const Vector2> positions)
{
RecalculateSupport(world, positions);
bool valid = true;
// Collect remaining positions with active support; flag any that lost all support
std::vector<Vector2> supportedPositions;
world.each([&](const TilePosition& tp, const Support& s) {
if (InSpan(positions, tp.Position)) return;
if (s.SupportsAvailable == 0)
valid = false;
else
supportedPositions.push_back(tp.Position);
});
// Check every RequiresSupport entity has an active support one tile below it
world.each([&](flecs::entity e, const TilePosition& tp) {
if (e.has<RequiresSupport>())
{
Vector2 supportPos = { tp.Position.X, tp.Position.Y - 1 };
tcb::span<const Vector2> suppSpan(supportedPositions.data(), supportedPositions.size());
if (!InSpan(suppSpan, supportPos))
valid = false;
}
});
RecalculateSupport(world);
return valid;
}
void Flecs_Support(flecs::world& world)
{
world.component<Support>()
.member<uint8_t>("MaxSupport")
.member<uint8_t>("SupportsAvailable");
world.component<GroundedSupport>();
world.component<RequiresSupport>();
// OnSet fires after the value is written (unlike OnAdd which fires during archetype move,
// before the actual struct values are copied in — MaxSupport would still be 0 at that point).
world.observer<Support>("On Support Set")
.event(flecs::OnSet)
.each([](flecs::entity e, Support&) {
auto w = e.world();
RecalculateSupport(w);
});
world.observer<Support>("On Support Remove")
.event(flecs::OnRemove)
.each([](flecs::entity e, Support&) {
auto w = e.world();
if (e.has<TilePosition>())
{
Vector2 pos = e.get<TilePosition>().Position;
RecalculateSupport(w, tcb::span<const Vector2>(&pos, 1));
}
else
{
RecalculateSupport(w);
}
});
// GroundedSupport is a tag (no data) — use .with<>() filter so the lambda
// takes only flecs::entity, avoiding the empty-type column issue.
world.observer("On GroundedSupport Add")
.with<GroundedSupport>()
.event(flecs::OnAdd)
.each([](flecs::entity e) {
auto w = e.world();
RecalculateSupport(w);
});
// OnRemove fires before actual removal, so entity still has GroundedSupport.
// Pass position to unground so the entity is not treated as a grounded seed.
world.observer("On GroundedSupport Remove")
.with<GroundedSupport>()
.event(flecs::OnRemove)
.each([](flecs::entity e) {
auto w = e.world();
if (e.has<TilePosition>())
{
Vector2 pos = e.get<TilePosition>().Position;
RecalculateSupport(w, {}, tcb::span<const Vector2>(&pos, 1));
}
else
{
RecalculateSupport(w);
}
});
}

View File

247
src/Core/Chunk.cpp Normal file
View File

@@ -0,0 +1,247 @@
#include "Core/Chunk.h"
#include "Chunk.h"
void ChunkData::MarkAsPersistant(entt::entity entity)
{
auto removedIt = std::remove_if(Entities.begin(), Entities.end(), [entity](EntityTile& tile)
{
return tile.Entity == entity;
});
PersistantEntities.insert(PersistantEntities.end(), removedIt, Entities.end());
Entities.erase(removedIt, Entities.end());
}
void ChunkData::RemovePersistance(entt::entity entity)
{
auto removedIt = std::remove_if(PersistantEntities.begin(), PersistantEntities.end(), [entity](EntityTile& tile)
{
return tile.Entity == entity;
});
Entities.insert(Entities.end(), removedIt, PersistantEntities.end());
PersistantEntities.erase(removedIt, PersistantEntities.end());
}
const Chunk& ChunkCollection::GetChunk(int x, int y)
{
return GetChunk(ChunkKey{x, y});
}
const Chunk &ChunkCollection::GetChunk(ChunkKey key)
{
return GetChunkInternal(key);
}
Chunk const* ChunkCollection::TryGetChunk(int x, int y) const
{
return TryGetChunk(ChunkKey{x, y});
}
Chunk const *ChunkCollection::TryGetChunk(ChunkKey key) const
{
int chunkIndex = TryGetChunkIndex(key);
if (chunkIndex == -1)
return nullptr;
return ChunkDatas[chunkIndex].Chunk.get();
}
Tile ChunkCollection::GetTile(int x, int y)
{
return GetChunk(x, y).GetTile(Chunk::WorldToLocal(x), Chunk::WorldToLocal(y));
}
Tile const *ChunkCollection::TryGetTile(int x, int y) const
{
Chunk const* chunk = TryGetChunk(x, y);
int chunkX = Chunk::WorldToLocal(x);
int chunkY = Chunk::WorldToLocal(y);
return &chunk->GetTileRef(chunkX, chunkY);
}
ChunkData& ChunkCollection::GetChunkData(int x, int y)
{
return GetChunkData(ChunkKey{x, y});
}
ChunkData &ChunkCollection::GetChunkData(ChunkKey key)
{
int index = GetChunkIndex(key);
return ChunkDatas[index];
}
Tile &ChunkCollection::GetTileInternal(int x, int y)
{
return GetChunkInternal(x, y).GetTile(Chunk::WorldToLocal(x), Chunk::WorldToLocal(y));
}
void ChunkCollection::SetTile(Tile tile, int x, int y)
{
GetTileInternal(x, y) = tile;
}
void ChunkCollection::InvalidateCachedChunk()
{
CachedChunk = -1;
}
entt::entity ChunkCollection::GetEntity(int x, int y) const
{
int chunkIndex = TryGetChunkIndex(x, y);
int chunkX = Chunk::WorldToLocal(x);
int chunkY = Chunk::WorldToLocal(y);
if (chunkIndex != -1 &&
ChunkDatas[chunkIndex].Chunk->GetTile(chunkX, chunkY).HasEntity())
{
for (auto& entity : ChunkDatas[chunkIndex].Entities)
{
if (entity.ChunkX == chunkX && entity.ChunkY == chunkY)
return entity.Entity;
}
DEV_ASSERT(false); // should be unreachable
}
return entt::null;
}
int ChunkCollection::GetChunkIndex(int x, int y)
{
return GetChunkIndex(ChunkKey{x, y});
}
int ChunkCollection::GetChunkIndex(ChunkKey key)
{
if (key == CachedChunkKey && CachedChunk != -1)
return CachedChunk;
auto chunkIt = ChunkMap.find(key);
if (chunkIt != ChunkMap.end())
{
CachedChunkKey = key;
CachedChunk = chunkIt->value;
return chunkIt->value;
}
ChunkDatas.push_back({});
ChunkMap.insert(key, ChunkDatas.size() - 1);
CachedChunkKey = key;
CachedChunk = ChunkDatas.size() - 1;
return ChunkDatas.size() - 1;
}
int ChunkCollection::TryGetChunkIndex(int x, int y) const
{
return TryGetChunkIndex(ChunkKey{x, y});
}
int ChunkCollection::TryGetChunkIndex(ChunkKey key) const
{
if (key == CachedChunkKey && CachedChunk != -1)
return CachedChunk;
auto chunkIt = ChunkMap.find(key);
if (chunkIt != ChunkMap.end())
{
const_cast<ChunkKey&>(CachedChunkKey) = key;
const_cast<int&>(CachedChunk) = chunkIt->value;
return chunkIt->value;
}
return -1;
}
Chunk& ChunkCollection::GetChunkInternal(int x, int y)
{
return GetChunkInternal(ChunkKey{x, y});
}
Chunk &ChunkCollection::GetChunkInternal(ChunkKey key)
{
int index = GetChunkIndex(key);
return *ChunkDatas[index].Chunk.get();
}
void ChunkCollection::SetChunkTiles(int x, int y, std::unique_ptr<Chunk> &&chunk)
{
SetChunkTiles(ChunkKey{x, y}, std::move(chunk));
}
void ChunkCollection::SetChunkTiles(ChunkKey key, std::unique_ptr<Chunk> &&chunk)
{
GetChunkData(key).Chunk = std::move(chunk);
}
void ChunkCollection::AddEntity(entt::entity entity, const Vector<Vector2i> &claimedPositions)
{
for (auto& pos : claimedPositions)
{
int chunkX = Chunk::WorldToLocal(pos.x);
int chunkY = Chunk::WorldToLocal(pos.y);
auto& chunkData = GetChunkData(pos.x, pos.y);
chunkData.Entities.push_back(EntityTile{entity, chunkX, chunkY});
}
}
void ChunkCollection::AddPersistantEntity(entt::entity entity, const Vector<Vector2i> &claimedPositions)
{
for (auto& pos : claimedPositions)
{
int chunkX = Chunk::WorldToLocal(pos.x);
int chunkY = Chunk::WorldToLocal(pos.y);
auto& chunkData = GetChunkData(pos.x, pos.y);
chunkData.PersistantEntities.push_back(EntityTile{entity, chunkX, chunkY});
}
}
void ChunkCollection::MarkAsPersistant(entt::entity entity)
{
for (ChunkData& chunk : ChunkDatas)
{
chunk.MarkAsPersistant(entity);
}
}
void ChunkCollection::RemovePersistance(entt::entity entity)
{
for (ChunkData& chunk : ChunkDatas)
{
chunk.RemovePersistance(entity);
}
}
void ChunkCollection::RemoveEntity(entt::entity entity)
{
for (ChunkData& chunk : ChunkDatas)
{
chunk.Entities.erase(std::remove_if(chunk.Entities.begin(), chunk.Entities.end(), [entity](EntityTile& tile)
{
return tile.Entity == entity;
}));
chunk.PersistantEntities.erase(std::remove_if(chunk.PersistantEntities.begin(), chunk.PersistantEntities.end(), [entity](EntityTile& tile)
{
return tile.Entity == entity;
}));
}
}
void ChunkCollection::RemoveChunk(int x, int y)
{
RemoveChunk(ChunkKey{x, y});
}
void ChunkCollection::RemoveChunk(ChunkKey key)
{
auto it = ChunkMap.find(key);
if (it != ChunkMap.end())
{
ChunkDatas[it->value].Clear();
InvalidateCachedChunk();
}
}

View File

@@ -0,0 +1,16 @@
#include "Core/FactoryCommandQueue.h"
void FactoryCommandQueue::ExecuteAll(FactoryWorld& world)
{
std::scoped_lock lock(Mutex);
for (auto& command : Commands)
command.Command(world, command.Entity, command.Data);
ClearUnsafe();
}
void FactoryCommandQueue::Clear()
{
std::scoped_lock lock(Mutex);
ClearUnsafe();
}

920
src/Core/FactoryWorld.cpp Normal file
View File

@@ -0,0 +1,920 @@
#include "Core/FactoryWorld.h"
#include "modules/noise/fastnoise_lite.h"
#include "core/config/engine.h"
#include "core/templates/hash_set.h"
#include "scene/main/scene_tree.h"
#include "core/math/random_number_generator.h"
#include "Main/factory_server.h"
#include "Core/WorldGenerator.h"
#include "Core/Serialization.h"
#include "Nodes/Entity/FactoryEntity.h"
#include "Nodes/FactoryWorldInterface.h"
#include "Components/Sync.h"
#include "Components/Minion.h"
#include "Components/Position.h"
#include "Components/Support.h"
#include "Util/AStar.h"
#include <queue>
#include "FactoryWorld.h"
using namespace godot;
void FactoryWorld::Tick(int amount)
{
// dont tick sync and render systems when double processing a frame
int32_t framesDrawn = Engine::get_singleton()->get_frames_drawn();
int lastOrder = (framesDrawn == LastDrawnFrame) ? SystemBase::SYSTEM_EXECUTION_ORDER_SYNC : INT32_MAX;
WorldAccessMutex.lock();
CommandQueue->ExecuteAll(Registry);
auto& systems = Systems->GetSystems();
for (auto it{systems.begin()}; it != systems.end() && (*it)->GetExecutionOrder() < lastOrder; ++it)
{
(*it)->Tick(Registry);
}
WorldAccessMutex.unlock();
//Object::cast_to<SceneTree>(Engine::get_singleton()->get_main_loop());
}
static constexpr auto WorldSavePath = "user://world";
void FactoryWorld::Initialize(FactoryWorldInterface* worldInterface)
{
Interface = worldInterface;
auto& systemGenerators = FactoryServer::get_singleton()->GetSystemGenerators();
WorldSettings = FactoryServer::get_singleton()->GetSettings();
auto loader = WorldLoader{ *this };
if (!loader.LoadFile(WorldSavePath))
{
WorldInventory = { WorldSettings->Items };
auto random = Ref<RandomNumberGenerator>{};
random.instantiate();
Seed = random->randi();
}
for (auto& generator : systemGenerators)
{
Systems->RegisterSystem(generator());
}
}
void FactoryWorld::Save()
{
auto saver = WorldSerializer{ *this };
saver.SerializeFile(WorldSavePath);
}
void FactoryWorld::SetInventory(const Vector<Ref<ItemConfig>>& items)
{
WorldInventory = { items };
}
Inventory FactoryWorld::GetWorldInventory(Vector2 position) const
{
auto view = Registry.view<const TilePosition, const InventoryAreaOfEffect, const Inventory>();
int minDistance = INFINITY;
Inventory closestInventory = WorldInventory;
for (auto [ent, pos, area, inv] : view.each())
{
if (position.x >= pos.Position.x - area.Size &&
position.x <= pos.Position.x + area.Size &&
position.y >= pos.Position.y - area.Size &&
position.y <= pos.Position.y + area.Size)
{
int distanceSQ = pos.Position.distance_squared_to(position);
if ((!area.IsCircle || distanceSQ <= area.Size * area.Size) &&
distanceSQ < minDistance)
{
minDistance = distanceSQ;
closestInventory = inv;
}
}
}
return closestInventory;
}
Inventory FactoryWorld::GetWorldInventory(entt::entity entity) const
{
auto position = Registry.get<TilePosition>(entity);
return GetWorldInventory(position.Position);
}
bool FactoryWorld::SupportCheckerHelper(entt::entity entity) const
{
auto support = Registry.try_get<Support>(entity);
return support && support->SupportsAvailable > 0;
}
uint8_t FactoryWorld::SupportValueHelper(entt::entity entity) const
{
auto support = Registry.try_get<Support>(entity);
return support ? support->SupportsAvailable : 0;
}
uint8_t FactoryWorld::GetSupportValue(int x, int y) const
{
auto entity = Chunks.GetEntity(x, y);
if (entity != entt::null)
return SupportValueHelper(entity);
return 0;
}
bool FactoryWorld::CheckIfSupportHelper(entt::entity entity, Support& support) const
{
auto pSupport = Registry.try_get<Support>(entity);
if (pSupport)
support = *pSupport;
return pSupport;
}
void FactoryWorld::ConnectSupports(Vector2i pos, Support& support, Vector2i direction)
{
}
// FactoryError FactoryWorld::CanPlaceSupport(int x, int y) const
// {
// {
// auto bottomTile = TryGetTile(x, y - 1);
// if (bottomTile && (bottomTile->IsFiller() || (bottomTile->HasEntity() && SupportCheckerHelper(GetEntity(x, y - 1)))))
// return FACTORY_ERROR_NONE;
// } {
// auto leftTile = TryGetTile(x - 1, y);
// if (leftTile && (leftTile->IsFiller() || (leftTile->HasEntity() && SupportCheckerHelper(GetEntity(x - 1, y)))))
// return FACTORY_ERROR_NONE;
// } {
// auto rightTile = TryGetTile(x + 1, y);
// if (rightTile && (rightTile->IsFiller() || (rightTile->HasEntity() && SupportCheckerHelper(GetEntity(x + 1, y)))))
// return FACTORY_ERROR_NONE;
// }
// return FACTORY_ERROR_REQUIRES_SUPPORT;
// }
// FactoryError FactoryWorld::CanRemoveSupport(int x, int y) const
// {
// auto supportTile = TryGetTile(x, y);
// Support support{};
// if (!supportTile || !supportTile->HasEntity() || CheckIfSupportHelper(GetEntity(x, y), support))
// return FACTORY_ERROR_INVALID_POS;
// if (support.SupportsUp)
// {
// auto topTile = TryGetTile(x, y + 1);
// auto entity = GetEntity(x, y + 1);
// if (topTile &&
// topTile->HasEntity() &&
// !Registry.all_of<RequiresSupport>(entity) &&
// support.SupportsAvailable > SupportValueHelper(entity) &&
// GetSupportValue(x - 1, y + 1) == 0 &&
// GetSupportValue(x + 1, y + 1) == 0)
// return FACTORY_ERROR_REQUIRES_SUPPORT;
// }
// if (support.SupportsLeft)
// {
// auto leftTile = TryGetTile(x - 1, y);
// auto entity = GetEntity(x - 1, y);
// if (leftTile &&
// leftTile->HasEntity() &&
// support.SupportsAvailable > SupportValueHelper(entity) &&
// GetSupportValue(x - 2, y) == 0 &&
// GetSupportValue(x - 1, y - 1) == 0)
// return FACTORY_ERROR_REQUIRES_SUPPORT;
// }
// if (support.SupportsRight)
// {
// auto rightTile = TryGetTile(x + 1, y);
// auto entity = GetEntity(x + 1, y);
// if (rightTile &&
// rightTile->HasEntity() &&
// support.SupportsAvailable > SupportValueHelper(entity) &&
// GetSupportValue(x + 2, y) == 0 &&
// GetSupportValue(x + 1, y - 1) == 0)
// return FACTORY_ERROR_REQUIRES_SUPPORT;
// }
// return FACTORY_ERROR_NONE;
// }
// void FactoryWorld::RegisterSupport(int x, int y, Support& support)
// {
// DEV_ASSERT(support.MaxSupport != 0);
// DEV_ASSERT(CanPlaceSupport(x, y) == FACTORY_ERROR_NONE);
// //auto& tile = GetTileInternal(x, y);
// {
// auto bottomTile = GetTile(x, y - 1);
// if (bottomTile.IsFiller())
// {
// support.SupportsAvailable = support.MaxSupport;
// return;
// }
// if (bottomTile.HasEntity())
// {
// auto entity = GetEntity(x, y - 1);
// auto bottomSupport = Registry.try_get<Support>(entity);
// if (bottomSupport && bottomSupport->SupportsAvailable > 0)
// {
// support.SupportsAvailable = std::max<uint8_t>(support.SupportsAvailable, bottomSupport->SupportsAvailable - 1);
// bottomSupport->SupportsUp = true;
// support.SupportedByBottom = true;
// }
// }
// } {
// auto leftTile = GetTile(x - 1, y);
// if (leftTile.IsFiller())
// {
// support.SupportsAvailable = support.MaxSupport;
// return;
// }
// if (leftTile.HasEntity())
// {
// auto entity = GetEntity(x - 1, y);
// auto leftSupport = Registry.try_get<Support>(entity);
// if (leftSupport && leftSupport->SupportsAvailable > 0)
// {
// support.SupportsAvailable = std::max<uint8_t>(support.SupportsAvailable, leftSupport->SupportsAvailable - 1);
// leftSupport->SupportsRight = true;
// support.SupportedByLeft = true;
// }
// }
// } {
// auto rightTile = GetTile(x + 1, y);
// if (rightTile.IsFiller())
// {
// support.SupportsAvailable = support.MaxSupport;
// return;
// }
// if (rightTile.HasEntity())
// {
// auto entity = GetEntity(x + 1, y);
// auto rightSupport = Registry.try_get<Support>(entity);
// if (rightSupport && rightSupport->SupportsAvailable > 0)
// {
// support.SupportsAvailable = std::max<uint8_t>(support.SupportsAvailable, rightSupport->SupportsAvailable - 1);
// rightSupport->SupportsLeft = true;
// support.SupportedByRight = true;
// }
// }
// }
// support.SupportsAvailable = std::min(support.SupportsAvailable, support.MaxSupport);
// }
// void FactoryWorld::RemoveSupport(int x, int y)
// {
// DEV_ASSERT(CanRemoveSupport(x, y) == FACTORY_ERROR_NONE);
// DEV_ASSERT(Registry.all_of<Support>(GetEntity(x, y)));
// Support& support = Registry.get<Support>(GetEntity(x, y));
// }
FactoryError FactoryWorld::CanPlaceEntity(int x, int y, Ref<Archetype> archetype)
{
if (archetype.is_valid() && archetype->Scene.is_valid())
{
auto& chunk = Chunks.GetChunkData(x, y);
for (auto& condition : archetype->SpawnConditions)
{
if (condition->IsValid(Chunks, Vector2i{x, y}))
return FACTORY_ERROR_NO_SPACE;
}
Vector<Vector2i> claimedCoordinates{};
for (auto& condition : archetype->SpawnConditions)
{
condition->ClaimTiles(claimedCoordinates, Chunks, Vector2i{x, y});
}
for (auto coordinate : claimedCoordinates)
{
for (auto entity : chunk.Entities)
{
if (entity.ChunkX == coordinate.x && entity.ChunkY == coordinate.y)
return FACTORY_ERROR_NO_SPACE;
}
}
return FACTORY_ERROR_NONE;
}
return FACTORY_ERROR_NO_SPACE;
}
FactoryError FactoryWorld::AddEntity(int x, int y, Ref<Archetype> archetype)
{
entt::entity createdEntity = CreateEntity();
return AddEntity(x, y, archetype, createdEntity);
}
void FactoryWorld::RemoveEntity(FactoryEntity *node)
{
RemoveEntity(node->GetEntity());
}
void FactoryWorld::RemoveEntity(int x, int y)
{
RemoveEntity(Chunks.GetEntity(x, y));
}
void FactoryWorld::RemoveEntity(entt::entity entity)
{
if (entity == entt::null || !Registry.all_of<NodePtr, ArchetypePtr, TilePosition>(entity))
return;
auto&& [node, arch, pos] = Registry.get<NodePtr, ArchetypePtr, TilePosition>(entity);
node.Node->queue_free();
Chunks.RemoveEntity(entity);
Registry.destroy(entity);
}
FactoryError FactoryWorld::AddEntity(int x, int y, Ref<Archetype> archetype, entt::entity entityID)
{
auto canPlace = CanPlaceEntity(x, y, archetype);
if (canPlace != FACTORY_ERROR_NONE)
return canPlace;
// instantiate node
Node2D* node = Object::cast_to<Node2D>(archetype->Scene->instantiate());
auto factoryNode = Object::cast_to<FactoryEntity>(node);
DEV_ASSERT(node);
// set position in ecs
auto tilePos = TilePosition();
tilePos.Position = Vector2i(x, y);
Registry.emplace<TilePosition>(entityID, tilePos);
// link archetype to the entity
ArchetypePtr archPtr{};
archPtr.Archetype = archetype;
Registry.emplace<ArchetypePtr>(entityID, archPtr);
// Add Level component if archetype has levels
if (!archetype->Upgrades.is_empty())
{
Registry.emplace<Level>(entityID);
}
// register the entities in the chunks
Vector<Vector2i> locations{};
for (auto& condition : archetype->SpawnConditions)
{
condition->ClaimTiles(locations, Chunks, Vector2i{x, y});
}
Chunks.AddEntity(entityID, locations);
// TODO add to chunk node instead of world
Interface->add_child(node);
node->set_position(Vector2(x + 0.5f, -y + 1));
if (factoryNode)
{
// set node ptr in ecs
NodePtr nodePtr{};
nodePtr.Node = factoryNode;
Registry.emplace<NodePtr>(entityID, nodePtr);
factoryNode->SetCommandQueue(CommandQueue);
factoryNode->SetEntity(entityID);
// initialize data in ecs
factoryNode->Initialize(Registry, *this, entityID);
}
return FACTORY_ERROR_NONE;
}
entt::entity FactoryWorld::CreateEntity()
{
return Registry.create();
}
bool FactoryWorld::IsValidCameraPos(Rect2i viewport) const
{
auto pos0 = viewport.position;
auto pos3 = viewport.get_end();
auto pos1 = Vector2i{ pos0.x, pos3.y };
auto pos2 = Vector2i{ pos3.x, pos0.y };
bool valid0{};
bool valid1{};
bool valid2{};
bool valid3{};
for (auto chunk : UnlockedChunks)
{
auto bounds = chunk.GetBounds();
valid0 = valid0 || bounds.has_point(pos0);
valid1 = valid1 || bounds.has_point(pos1);
valid2 = valid2 || bounds.has_point(pos2);
valid3 = valid3 || bounds.has_point(pos3);
}
return valid0 && valid1 && valid2 && valid3;
}
FactoryError FactoryWorld::TryUnlockChunk(ChunkKey chunk)
{
for (auto& unlockableChunk : UnlockableChunks)
{
if (unlockableChunk.ChunkID == chunk)
{
for (auto& requirement : unlockableChunk.Items)
if (WorldInventory.GetItemsAmount(requirement.Item.Item) < requirement.Amount)
return FACTORY_ERROR_NOT_ENOUGH_ITEMS;
for (auto& requirement : unlockableChunk.Items)
WorldInventory.RemoveItems(requirement.Item);
UnlockedChunks.push_back(chunk);
RefreshUnlockedChunks();
return FACTORY_ERROR_NONE;
}
}
return FACTORY_ERROR_INVALID_POS;
}
void FactoryWorld::RefreshUnlockedChunks()
{
UnlockedChunks.clear();
if (WorldSettings->LayerConfigs.is_empty()) return;
HashSet<ChunkKey> unlockableChunks{};
int maxHeight = WorldSettings->LayerConfigs[0]->StartChunk;
for (auto& unlockedChunk : UnlockedChunks)
{
ChunkKey chunk0 = unlockedChunk;
ChunkKey chunk1 = unlockedChunk;
ChunkKey chunk2 = unlockedChunk;
ChunkKey chunk3 = unlockedChunk;
chunk0.X -= 1; chunk1.X += 1;
chunk2.Y -= 1; chunk3.Y += 1;
unlockableChunks.insert(chunk0);
unlockableChunks.insert(chunk1);
unlockableChunks.insert(chunk2);
unlockableChunks.insert(chunk3);
}
for (auto& unlockedChunk : UnlockedChunks)
{
unlockableChunks.erase(unlockedChunk);
}
for (auto& unlockable : unlockableChunks)
{
UnlockedChunks.push_back(unlockable);
}
}
// void FactoryWorld::PlaceLight(LightValue light, Vector2i position)
// {
// PropogateLight(light, position);
// Interface->call_deferred("update_lightmap");
// }
// void FactoryWorld::RefreshLightMap()
// {
// auto lights = Registry.view<LightValue, TilePosition>();
// // Find World Bounds
// Bounds = Bounds.merge(Rect2i(-256, -256, 512, 300));
// lights.each([this](LightValue light, TilePosition position)
// {
// Rect2i lightRadius = Rect2i(position.Position - Vector2i(light.Val, light.Val), 2.f * light.Val * Vector2i(1, 1));
// Bounds = Bounds.merge(lightRadius);
// });
// // reset light chunks
// for (int i{}; i < Chunks.size(); ++i)
// for (int j{}; j < Chunks[i].LightChunk.Tiles.size(); ++j)
// Chunks[i].LightChunk.Tiles[j].Val = 0;
// // skylight
// ApplySkyLight(Bounds);
// // propogate all light sources
// lights.each([this](LightValue light, TilePosition position)
// {
// PropogateLight(light, position.Position);
// });
// // sync bounds with interface
// Interface->call_deferred("set_bounds", Bounds);
// }
// void FactoryWorld::ApplySkyLight(Rect2i bounds)
// {
// int lowestLightval = INT32_MAX;
// int highestLightVal = INT32_MIN;
// // go down until block is found
// for (int x{ bounds.position.x }; x <= bounds.get_end().x; ++x)
// for (int y{ bounds.get_end().y }; y >= bounds.position.y; --y)
// {
// auto tileData = GetTileData(x, y);
// if (tileData.Tile.IsTile())
// {
// lowestLightval = std::min(lowestLightval, y + 1);
// highestLightVal = std::max(highestLightVal, y + 1);
// break;
// }
// tileData.Light.Val = LightValue::MaxLightVal;
// }
// // spread each light block
// for (int x{ bounds.position.x }; x <= bounds.get_end().x; ++x)
// for (int y{ lowestLightval }; y <= highestLightVal; ++y)
// {
// LightValue& value = GetLightValueInternal(x, y);
// if (value.Val == LightValue::MaxLightVal)
// {
// auto spreadValue = value;
// value.Val = 0;
// PropogateLight(spreadValue, Vector2i(x, y));
// }
// }
// Interface->call_deferred("update_lightmap");
// }
// void FactoryWorld::PropogateLight(LightValue light, Vector2i position)
// {
// // light value of tile
// LightValue& lightVal = GetLightValueInternal(position.x, position.y);
// int previousVal = lightVal.Val;
// lightVal.Val = std::max(light.Val, lightVal.Val);
// // new light value decided by taking substracting the penetration
// int newLightVal = light.Val - std::max(1, lightVal.Penetration / light.Penetration);
// if (light.Val > previousVal && newLightVal >= 0)
// {
// light.Val = newLightVal;
// // check neighboring tiles
// PropogateLight(light, position + Vector2i(0, +1));
// PropogateLight(light, position + Vector2i(0, -1));
// PropogateLight(light, position + Vector2i(+1, 0));
// PropogateLight(light, position + Vector2i(-1, 0));
// }
// }
FactoryError FactoryWorld::TryUpgradeEntity(FactoryEntity* entity)
{
return TryUpgradeEntity(entity->GetEntity());
}
FactoryError FactoryWorld::TryUpgradeEntity(entt::entity entity)
{
auto upgradeError = CanUpgradeEntity(entity);
if (upgradeError)
return upgradeError;
Level const* level = Registry.try_get<Level>(entity);
auto archetype = Registry.get<ArchetypePtr>(entity);
// remove ingredients
auto newLevelIngredients = archetype.Archetype->Upgrades[level->Val];
{
auto inventory = FactoryServer::get_singleton()->AccessInventory();
for (auto ingredient : newLevelIngredients->UpgradeCost)
{
inventory->RemoveItems(ingredient->Item->Item.ItemID, ingredient->Amount);
}
}
UpgradeEntity(entity, archetype.Archetype);
return FACTORY_ERROR_NONE;
}
FactoryError FactoryWorld::TryUpgradeEntity(const Vector2i& position)
{
auto entity = Chunks.GetEntity(position.x, position.y);
if (entity != entt::null)
return TryUpgradeEntity(entity);
return FACTORY_ERROR_NOT_ENTITY;
}
FactoryError FactoryWorld::CanUpgradeEntity(entt::entity entity) const
{
Level const* level = Registry.try_get<Level>(entity);
auto archetype = Registry.get<ArchetypePtr>(entity);
if (!level || archetype.Archetype.is_null())
return FACTORY_ERROR_CANT_UPGRADE;
if (level->Val >= archetype.Archetype->Upgrades.size())
return FACTORY_ERROR_FULLY_UPGRADED;
auto newLevelIngredients = archetype.Archetype->Upgrades[level->Val];
{
auto inventory = FactoryServer::get_singleton()->AccessInventory();
for (auto ingredient : newLevelIngredients->UpgradeCost)
{
if (inventory->GetItemsAmount(ingredient->Item->Item.ItemID) < ingredient->Amount)
return FACTORY_ERROR_NOT_ENOUGH_ITEMS;
}
}
auto upgradeError = Registry.get<NodePtr>(entity).Node->CanUpgrade();
if (upgradeError != FACTORY_ERROR_NONE)
return upgradeError;
return FACTORY_ERROR_NONE;
}
FactoryError FactoryWorld::CanUpgradeEntity(FactoryEntity* entity) const
{
return CanUpgradeEntity(entity->GetEntity());
}
FactoryError FactoryWorld::CanUpgradeEntity(const Vector2i& position)
{
auto entity = Chunks.GetEntity(position.x, position.y);
if (entity != entt::null)
return CanUpgradeEntity(entity);
return FACTORY_ERROR_NOT_ENTITY;
}
void FactoryWorld::UpgradeEntity(entt::entity entity, Ref<Archetype> archetype)
{
FactoryEntity* node = Registry.get<NodePtr>(entity).Node;
auto& level = Registry.get<Level>(entity);
Registry.emplace<SaveLoad>(entity);
auto newLevelIngredients = archetype->Upgrades[level.Val];
int newLevel = ++level.Val;
node->SetLevel(newLevel, newLevelIngredients);
}
void FactoryWorld::SetEntityLevel(entt::entity entity, uint8_t level)
{
SetEntityLevel(entity, Registry.get<ArchetypePtr>(entity).Archetype, level);
}
void FactoryWorld::SetEntityLevel(entt::entity entity, Ref<Archetype> archetype, uint8_t level)
{
FactoryEntity* node = Registry.get<NodePtr>(entity).Node;
auto& lvl = Registry.get<Level>(entity);
if (lvl.Val != level)
{
lvl = Level(level);
node->SetLevel(level, archetype->Upgrades[level]);
}
}
void FactoryWorld::HighlightUpgradableEntities(TileMapLayer* tilemap) const
{
tilemap->clear();
auto view = Registry.view<const Level, const ArchetypePtr, const TilePosition>();
view.each([tilemap](Level lvl, ArchetypePtr ptr, TilePosition pos) {
if (!ptr.Archetype->OccupiedTiles.is_empty() &&
lvl.Val < ptr.Archetype->Upgrades.size())
{
for (const auto& tile : ptr.Archetype->OccupiedTiles)
{
Vector2i position = pos.Position + tile->Offset;
position.y = -position.y;
tilemap->set_cell(position, 0, Vector2i(0, 0));
}
}
});
}
void FactoryWorld::UpgradeEntity(entt::entity entity)
{
UpgradeEntity(entity, Registry.get<ArchetypePtr>(entity).Archetype);
}
struct WorldAStarNode
{
public:
WorldAStarNode(const FactoryWorld& world, Vector2i pos) : World{ &world }, Position{ pos } {};
WorldAStarNode() = default;
WorldAStarNode(const WorldAStarNode&) = default;
WorldAStarNode(WorldAStarNode&&) = default;
WorldAStarNode& operator=(const WorldAStarNode&) = default;
WorldAStarNode& operator=(WorldAStarNode&&) = default;
public:
// Heuristic function which computes the estimated cost to the goal node
float GoalDistanceEstimate(const WorldAStarNode& nodeGoal) const
{
return static_cast<float>(Position.distance_squared_to(nodeGoal.Position));
}
// Returns true if this node is the goal node
bool IsGoal(const WorldAStarNode& nodeGoal) const
{
return Position == nodeGoal.Position;
}
// Retrieves all successors to this node and adds them via astarsearch.addSuccessor()
bool GetSuccessors(AStarSearch<WorldAStarNode>* astarsearch, WorldAStarNode* parent_node) const
{
Vector2i parentPos{};
if (parent_node)
{
parentPos = parent_node->Position;
}
Vector2i bottomPos = Vector2i(Position.x, Position.y - 1);
Vector2i leftPos = Vector2i(Position.x - 1, Position.y);
Vector2i rightPos = Vector2i(Position.x + 1, Position.y);
auto bottomTile = World->TryGetTile(bottomPos.x, bottomPos.y);
auto leftTile = World->TryGetTile(leftPos.x, leftPos.y);
auto rightTile = World->TryGetTile(rightPos.x, rightPos.y);
if (bottomPos != parentPos && bottomTile && !bottomTile->IsFiller())
astarsearch->AddSuccessor(WorldAStarNode(*World, bottomPos));
if (leftPos != parentPos && leftTile && !leftTile->IsFiller())
astarsearch->AddSuccessor(WorldAStarNode(*World, leftPos));
if (rightPos != parentPos && rightTile && !rightTile->IsFiller())
astarsearch->AddSuccessor(WorldAStarNode(*World, rightPos));
return true;
}
// Computes the cost of travelling from this node to the successor node
float GetCost(const WorldAStarNode& successor) const
{
return 1.f;
}
// Returns true if this node is the same as the rhs node
bool IsSameState(const WorldAStarNode& rhs) const
{
return rhs.Position == Position;
}
// Returns a hash for the state
size_t Hash() const
{
static_assert(sizeof(size_t) == sizeof(Vector2i));
return reinterpret_cast<const size_t&>(Position);
}
public:
FactoryWorld const* World;
Vector2i Position{};
};
FactoryError FactoryWorld::FindChutePath(Vector<Vector2i>& path, Vector2i startPos, Vector2i endPos) const
{
constexpr int MaxLength = 128;
if (startPos.distance_squared_to(endPos) > MaxLength * MaxLength)
return FACTORY_ERROR_PATH_TOO_LONG;
auto tile = TryGetTile(startPos.x, startPos.y);
if (!tile || !tile->IsAir())
return FACTORY_ERROR_INVALID_POS;
tile = TryGetTile(endPos.x, endPos.y);
if (!tile || !tile->IsAir())
return FACTORY_ERROR_INVALID_POS;
if (endPos.y > startPos.y)
std::swap(endPos, startPos);
AStarSearch<WorldAStarNode> aStarSearch{MaxLength * MaxLength * 2};
aStarSearch.SetStartAndGoalStates(WorldAStarNode{ *this, startPos }, WorldAStarNode{ *this, endPos });
unsigned int searchState = -1;
do
{
searchState = aStarSearch.SearchStep();
} while (searchState == AStarSearch<WorldAStarNode>::SEARCH_STATE_SEARCHING);
switch (searchState)
{
case AStarSearch<WorldAStarNode>::SEARCH_STATE_FAILED:
case AStarSearch<WorldAStarNode>::SEARCH_STATE_INVALID:
case AStarSearch<WorldAStarNode>::SEARCH_STATE_NOT_INITIALISED:
case AStarSearch<WorldAStarNode>::SEARCH_STATE_OUT_OF_MEMORY:
default:
return FACTORY_ERROR_INVALID_PATH;
case AStarSearch<WorldAStarNode>::SEARCH_STATE_SUCCEEDED:
path.clear();
path.push_back(aStarSearch.GetSolutionStart()->Position);
while (true)
{
auto newCell = aStarSearch.GetSolutionNext();
if (unlikely(!newCell))
{
path.push_back(endPos);
break;
}
if (Raycast(path[path.size() - 1], newCell->Position))
path.push_back(newCell->Position);
}
aStarSearch.FreeSolutionNodes();
break;
}
aStarSearch.EnsureMemoryFreed();
return FACTORY_ERROR_NONE;
}
Tile const* FactoryWorld::Raycast(Vector2i startPos, Vector2i endPos) const
{
Vector2 direction = Vector2(endPos - startPos).normalized();
Vector2 deltaDist{ std::abs(1 / direction.x), std::abs(1 / direction.y) };
Vector2 step{ 1, 1 };
Vector2 sideDist{ 0.5f, 0.5f };
int mapX = startPos.x;
int mapY = startPos.y;
if (direction.x < 0)
{
step.x = -1;
}
if (direction.y < 0)
{
step.y = -1;
}
while (true)
{
if (sideDist.x < sideDist.y)
{
sideDist.x += deltaDist.x;
mapX += step.x;
}
else
{
sideDist.y += deltaDist.y;
mapY += step.y;
}
Tile const* cell = TryGetTile(mapX, mapY);
if (cell && !cell->IsAir())
{
return cell;
}
if (mapX < 0 || mapX >= endPos.x || mapY < 0 || mapY >= endPos.y)
{
return nullptr;
}
}
}
bool FactoryWorld::IsSupport(int x, int y) const
{
auto entity = GetEntity(x, y);
return entity != entt::null && Registry.try_get<Support>(entity);
}
bool FactoryWorld::IsSupport(entt::entity entity) const
{
return Registry.try_get<Support>(entity);
}

618
src/Core/WorldGenerator.cpp Normal file
View File

@@ -0,0 +1,618 @@
#include "Core/WorldGenerator.h"
#include "Core/FactoryWorld.h"
#include "WorldGenerator.h"
#include <bitset>
using namespace godot;
template <typename T>
void SetNoiseSeed(Vector<Ref<T>>& layers, int32_t seed)
{
for (auto& layer : layers)
{
if (layer->NoiseGenerator.is_null())
layer->NoiseGenerator.instantiate();
layer->NoiseGenerator->set_seed(seed);
}
}
// WorldGenerator::WorldGenerator(Ref<FactoryWorldSettings> settings, int32_t seed)
// : Settings{ settings }
// , Seed{ seed }
// {
// if (Settings.is_valid())
// {
// Graph = Settings->WorldGenerator;
// }
// }
// static Vector<TileGeneratorFunction> GetGraphFunctions(Ref<LayerConfig> layer, const WorldGraph& graph);
// static std::array<tcb::span<const TileGeneratorFunction>, TILE_TYPE::TILE_MAX> DivideTileTypes(const Vector<TileGeneratorFunction>& tiles);
struct TileGeneratorFunction final
{
WorldNodeBase* Function{};
Tile ReturnedTile{};
};
Vector<TileGeneratorFunction> GetGraphFunctions(Ref<LayerConfig> layer, const WorldGraph& graph)
{
if (layer.is_null()) return {};
Vector<TileGeneratorFunction> Generators{};
for (auto tileGen : layer->Tiles)
{
TileGeneratorFunction generator{};
generator.Function = graph.GetNode(tileGen->TileGenerator);
generator.ReturnedTile = tileGen->Tile->TileData;
Generators.push_back(generator);
}
return Generators;
}
std::array<tcb::span<const TileGeneratorFunction>, TILE_TYPE::TILE_MAX> DivideTileTypes(const Vector<TileGeneratorFunction>& tiles)
{
std::array<tcb::span<const TileGeneratorFunction>, TILE_TYPE::TILE_MAX> values{};
for (int i{}, j{}; i < TILE_TYPE::TILE_MAX; ++i)
{
values[i] = tcb::span<const TileGeneratorFunction>{tiles.ptr() + j, std::size_t{0}};
for (; j < tiles.size() && tiles[j].ReturnedTile.GetType() == static_cast<TILE_TYPE>(i); ++j)
{
values[i] = tcb::span<const TileGeneratorFunction>(values[i].data(), tiles.ptr() + j + 1);
}
}
return values;
}
void ApplyGeneratorFunctions(WorldNodeParameters& parameters, tcb::span<const TileGeneratorFunction> generators, tcb::span<const TileGeneratorFunction> nextGenerators = {})
{
if (generators.empty() && nextGenerators.empty()) return;
auto bounds = parameters.GetGenerationBounds();
auto chunkBounds = parameters.ChunkInfo.GetBounds();
auto buffer = WorldNodeParameters::TileArray{ *parameters.GeneratedTiles };
for (int y = bounds.position.y; y < bounds.get_end().y; ++y)
{
float topLayerSubtract = !nextGenerators.empty() ? std::clamp(static_cast<float>(y - chunkBounds.position.y) / Chunk::ChunkSize, 0.f, 1.f) : 0;
float bottomLayerSubstract = 1.f - topLayerSubtract;
for (int x = bounds.position.x; x < bounds.get_end().x; ++x)
{
parameters.X = x;
parameters.Y = y;
float maxVal{};
for (auto& gen : generators)
{
auto val = gen.Function->Evaluate(parameters).get_unsafe_float() - topLayerSubtract;
if (val > maxVal)
{
maxVal = val;
buffer[parameters.GetArrayIndex(x, y)] = gen.ReturnedTile;
}
}
for (auto& gen : nextGenerators)
{
auto val = gen.Function->Evaluate(parameters).get_unsafe_float() - bottomLayerSubstract;
if (val > maxVal)
{
maxVal = val;
buffer[parameters.GetArrayIndex(x, y)] = gen.ReturnedTile;
}
}
}
}
*parameters.GeneratedTiles = buffer;
}
bool IsPocket(const WorldNodeParameters::TileArray& tiles, Vector2i pos, Tile tile)
{
for (int x{pos.x}; x >= 0 && tiles[WorldNodeParameters::GetArrayIndex(x - 1, pos.y)].IsAir(); --x)
{
auto underneathTile = tiles[WorldNodeParameters::GetArrayIndex(x, pos.y - 1)];
if ((!underneathTile.IsFiller() && underneathTile != tile) || x - 1 < 0) return false;
}
for (int x{pos.x + 1}; x < Chunk::ChunkSize && tiles[WorldNodeParameters::GetArrayIndex(x + 1, pos.y)].IsAir(); ++x)
{
auto underneathTile = tiles[WorldNodeParameters::GetArrayIndex(x, pos.y - 1)];
if ((!underneathTile.IsFiller() && underneathTile != tile) || x + 1 >= Chunk::ChunkSize) return false;
}
return true;
}
void FillPocket(WorldNodeParameters::TileArray& tiles, Vector2i pos, Tile tile)
{
for (int x{pos.x}; x >= 0 && tiles[WorldNodeParameters::GetArrayIndex(x, pos.y)].IsAir(); --x)
{
tiles[WorldNodeParameters::GetArrayIndex(x, pos.y)] = tile;
}
for (int x{pos.x + 1}; x < Chunk::ChunkSize && tiles[WorldNodeParameters::GetArrayIndex(x, pos.y)].IsAir(); ++x)
{
tiles[WorldNodeParameters::GetArrayIndex(x, pos.y)] = tile;
}
}
void FlowLiquid(WorldNodeParameters& parameters, Vector2i pos, Tile liquidTile)
{
auto& tiles = *parameters.GeneratedTiles;
if (tiles[parameters.GetArrayIndex(pos.x, pos.y)].IsFiller()) return;
for (int y{pos.y}; y >= 0; --y)
{
if (tiles[parameters.GetArrayIndex(pos.x, y - 1)].IsFiller())
{
for (int upperBounds{y + 2}; y <= pos.y && y < upperBounds && IsPocket(tiles, Vector2i{pos.x, y}, liquidTile); ++y)
{
FillPocket(tiles, Vector2i{pos.x, y}, liquidTile);
}
return;
}
}
}
void ApplyLiquids(WorldNodeParameters& parameters, tcb::span<const TileGeneratorFunction> generators, tcb::span<const TileGeneratorFunction> nextGenerators = {})
{
auto chunkCenter = parameters.ChunkInfo.GetBounds().get_center();
parameters.X = chunkCenter.x;
parameters.Y = chunkCenter.y;
for (auto& generator : generators)
{
int frequency = std::clamp(static_cast<int>(Chunk::ChunkSize * static_cast<float>(generator.Function->Evaluate(parameters))), 1, Chunk::ChunkSize);
for (int y{ Chunk::ChunkSize - 1 }; y >= (nextGenerators.empty() ? 0 : (Chunk::ChunkSize / 2)); y -= frequency)
{
for (int x{frequency / 2}; x < Chunk::ChunkSize; x += frequency)
{
FlowLiquid(parameters, Vector2i(x, y), generator.ReturnedTile);
}
}
}
for (auto& generator : nextGenerators)
{
int frequency = std::clamp(static_cast<int>(Chunk::ChunkSize * static_cast<float>(generator.Function->Evaluate(parameters))), 1, Chunk::ChunkSize);
for (int y{ Chunk::ChunkSize / 2 }; y >= 0; y -= frequency)
{
for (int x{frequency / 2}; x < Chunk::ChunkSize; x += frequency)
{
FlowLiquid(parameters, Vector2i(x, y), generator.ReturnedTile);
}
}
}
}
// bool WorldGenerator::GenerateChunk(ChunkKey chunkKey, Chunk& chunk) const
// {
// auto [currentLayer, nextLayer] = GetLayers(chunkKey);
// if (currentLayer.is_null()) return false;
// return GenerateChunk(chunkKey, chunk, currentLayer, nextLayer);
// }
// bool WorldGenerator::GenerateChunk(ChunkKey chunkKey, Chunk &chunk, Ref<LayerConfig> layer, Ref<LayerConfig> nextLayer) const
// {
// Vector<TileGeneratorFunction> Generators{GetGraphFunctions(layer, Graph)};
// Vector<TileGeneratorFunction> NextGenerators{GetGraphFunctions(nextLayer, Graph)};
// auto generatorLayers = DivideTileTypes(Generators);
// auto nextGeneratorLayers = DivideTileTypes(NextGenerators);
// WorldNodeParameters Parameters{};
// Parameters.ChunkInfo = chunkKey;
// Parameters.FinalValueSubstract = 0;
// Parameters.Seed = Seed;
// auto bounds = Parameters.GetGenerationBounds();
// WorldNodeParameters::TileArray buffer{};
// int chunkStart = chunkKey.GetBounds().position.y;
// ApplyGeneratorFunctions(Parameters, generatorLayers[TILE_AIR], nextGeneratorLayers[TILE_AIR]);
// ApplyGeneratorFunctions(Parameters, generatorLayers[TILE_FILLER], nextGeneratorLayers[TILE_FILLER]);
// ApplyLiquids(Parameters, generatorLayers[TILE_LIQUID], nextGeneratorLayers[TILE_LIQUID]);
// ApplyGeneratorFunctions(Parameters, generatorLayers[TILE_ORE], nextGeneratorLayers[TILE_ORE]);
// ApplyGeneratorFunctions(Parameters, generatorLayers[TILE_NPC], nextGeneratorLayers[TILE_NPC]);
// ApplyGeneratorFunctions(Parameters, generatorLayers[TILE_PLANT], nextGeneratorLayers[TILE_PLANT]);
// for (int y{}; y < Chunk::ChunkSize; ++y)
// {
// for (int x{}; x < Chunk::ChunkSize; ++x)
// {
// chunk.Tiles[y * Chunk::ChunkSize + x] = Parameters.GeneratedTiles[(y + WorldNodeParameters::MaxQueryOffset) * WorldNodeParameters::PaddedChunkSide + (x + WorldNodeParameters::MaxQueryOffset)];
// }
// }
// return true;
// }
// Vector<WorldGenerator::SpawnedEntities> WorldGenerator::SpawnEntities(ChunkKey chunkKey, Chunk &chunk, const std::vector<EntityTile>& persistantEntities) const
// {
// auto [currentLayer, nextLayer] = GetLayers(chunkKey);
// if (currentLayer.is_null()) return {};
// return SpawnEntities(chunkKey, chunk, currentLayer, nextLayer, persistantEntities);
// }
// Vector<WorldGenerator::SpawnedEntities> WorldGenerator::SpawnEntities(ChunkKey chunkKey, Chunk &chunk, Ref<LayerConfig> layer, Ref<LayerConfig> nextLayer, const std::vector<EntityTile>& persistantEntities) const
// {
// }
// struct ThreadParameters
// {
// WorldGenerator Generator{};
// ChunkKey ChunkKey{};
// std::function<void(ChunkData&&)> Callback{};
// std::vector<EntityTile> PersistantEntities{};
// };
// void ThreadedGenerateChunk_Internal(void* pData)
// {
// auto parameters = static_cast<ThreadParameters*>(pData);
// ChunkData data{};
// data.Chunk = std::make_unique<Chunk>();
// parameters->Generator.GenerateChunk(parameters->ChunkKey, *data.Chunk);
// auto entities = parameters->Generator.SpawnEntities(parameters->ChunkKey, *data.Chunk, parameters->PersistantEntities);
// for (int i{}; i < entities.size(); ++i)
// {
// auto entity = entities[i];
// for (auto pos : entity.ClaimedPositions)
// {
// data.Entities.push_back(EntityTile {
// entt::entity{i}, ChunkKey::WorldToChunk(pos.x), ChunkKey::WorldToChunk(pos.y)
// });
// }
// }
// data.PersistantEntities = std::move(std::move(parameters->PersistantEntities));
// parameters->Callback(std::move(data));
// memdelete(parameters);
// }
// void WorldGenerator::ThreadedGenerateChunk(ChunkKey chunkKey, std::function<void(ChunkData &&)> callback, const std::vector<EntityTile>& persistantEntities)
// {
// DEV_ASSERT(callback);
// ChunkData Chunk{};
// auto pParameters = memnew(ThreadParameters);
// pParameters->Callback = callback;
// pParameters->ChunkKey = chunkKey;
// pParameters->Generator = *this;
// pParameters->PersistantEntities = persistantEntities;
// WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_native_task(&ThreadedGenerateChunk_Internal, pParameters, false);
// }
Pair<Ref<LayerConfig>, Ref<LayerConfig>> ChunkGenerator::GetLayers() const
{
Ref<LayerConfig> currentLayer{};
Ref<LayerConfig> nextLayer{};
for (auto& layer : Settings->LayerConfigs)
{
if (layer->StartChunk > ChunkInfo.Y)
{
currentLayer = layer;
}
else if (layer->StartChunk == ChunkInfo.Y)
{
nextLayer = layer;
}
else if (layer->StartChunk < ChunkInfo.Y)
{
break;
}
}
return {currentLayer, nextLayer};
}
void ChunkGenerator::FillChunkCollection(int relativeX, int relativeY, ChunkCollection &collection) const
{
ChunkKey relativeKey{};
relativeKey.X = relativeX;
relativeKey.Y = relativeY;
auto bounds = relativeKey.GetBounds();
auto chunk = std::make_unique<Chunk>();
for (int y{ bounds.position.y }; y < bounds.get_end().y; ++y)
{
for (int x{ bounds.position.x }; x < bounds.get_end().x; ++x)
{
if (InBounds(x, y))
{
(*chunk).GetTile(Chunk::WorldToLocal(x), Chunk::WorldToLocal(y)) = GetTile(x, y);
}
}
}
ChunkKey key{ ChunkInfo };
key.X += relativeX;
key.Y += relativeY;
collection.SetChunkTiles(key, std::move(chunk));
}
Tile ChunkGenerator::GetTile(int x, int y) const
{
DEV_ASSERT(InBounds(x, y));
return (*TileArray)[(y + WorldNodeParameters::MaxQueryOffset) * WorldNodeParameters::PaddedChunkSide + (x + WorldNodeParameters::MaxQueryOffset)];
}
bool ChunkGenerator::InBounds(int x, int y) const
{
return x > -WorldNodeParameters::MaxQueryOffset && x < WorldNodeParameters::MaxQueryOffset + Chunk::ChunkSize &&
y > -WorldNodeParameters::MaxQueryOffset && y < WorldNodeParameters::MaxQueryOffset + Chunk::ChunkSize;
}
struct ThreadParameters
{
ChunkGenerator Generator;
ChunkGenerator::CreatedChunkCallback ChunkCallback;
ChunkGenerator::SpawnedEntitiesCallback EntitiesCallback;
ChunkGenerator::VisualizedChunkCallback VisualsCallback;
ChunkGenerator::ShadowsCallback ShadowsCallback;
};
void ChunkGenerator::GenerateChunk(Ref<FactoryWorldSettings> settings, ChunkKey chunkInfo, int seed, CreatedChunkCallback chunkCallback, SpawnedEntitiesCallback entitiesCallback, VisualizedChunkCallback visualsCallback, ShadowsCallback shadowsCallback)
{
auto parameters = memnew(ThreadParameters);
parameters->Generator = ChunkGenerator(settings, chunkInfo, seed);
parameters->ChunkCallback = chunkCallback;
parameters->EntitiesCallback = entitiesCallback;
parameters->VisualsCallback = visualsCallback;
parameters->ShadowsCallback = shadowsCallback;
WorkerThreadPool::get_singleton()->add_native_task(&ChunkGenerator::GenerateChunk, parameters, false, "Generating Chunk");
}
std::unique_ptr<Chunk> ChunkGenerator::GenerateChunkTilesNonThreaded()
{
std::unique_ptr<Chunk> returnVal;
GenerateChunkInternal([&returnVal](std::unique_ptr<Chunk>&& chunk)
{
returnVal = std::move(chunk);
}, {}, {}, {});
return std::move(returnVal);
}
void ChunkGenerator::GenerateChunk(void *pData)
{
ThreadParameters* parameters = static_cast<ThreadParameters*>(pData);
parameters->Generator.GenerateChunkInternal(parameters->ChunkCallback, parameters->EntitiesCallback, parameters->VisualsCallback, parameters->ShadowsCallback);
memdelete(parameters);
}
void ChunkGenerator::GenerateChunkInternal(CreatedChunkCallback chunkCallback, SpawnedEntitiesCallback entitiesCallback, VisualizedChunkCallback visualsCallback, ShadowsCallback shadowsCallback)
{
TileArray = std::make_unique<WorldNodeParameters::TileArray>();
GenerateChunkTiles();
if (chunkCallback)
{
auto chunk = std::make_unique<Chunk>();
for (int y{}; y < Chunk::ChunkSize; ++y)
{
for (int x{}; x < Chunk::ChunkSize; ++x)
{
chunk->Tiles[y * Chunk::ChunkSize + x] = (*TileArray)[(y + WorldNodeParameters::MaxQueryOffset) * WorldNodeParameters::PaddedChunkSide + (x + WorldNodeParameters::MaxQueryOffset)];
}
}
chunkCallback(std::move(chunk));
}
if (entitiesCallback)
{
auto entities = SpawnEntities();
entitiesCallback(entities);
}
if (visualsCallback)
{
auto visuals = CreateVisuals();
visualsCallback(visuals.get());
}
if (shadowsCallback)
{
auto shadowMap = CascadeShadows();
shadowsCallback(shadowMap.get());
}
}
void ChunkGenerator::GenerateChunkTiles() const
{
auto [currentLayer, nextLayer] = GetLayers();
Vector<TileGeneratorFunction> Generators{GetGraphFunctions(currentLayer, Settings->WorldGenerator)};
Vector<TileGeneratorFunction> NextGenerators{GetGraphFunctions(nextLayer, Settings->WorldGenerator)};
auto generatorLayers = DivideTileTypes(Generators);
auto nextGeneratorLayers = DivideTileTypes(NextGenerators);
WorldNodeParameters Parameters{};
Parameters.ChunkInfo = ChunkInfo;
Parameters.FinalValueSubstract = 0;
Parameters.Seed = Seed;
Parameters.GeneratedTiles = TileArray.get();
auto bounds = Parameters.GetGenerationBounds();
WorldNodeParameters::TileArray buffer{};
int chunkStart = ChunkInfo.GetBounds().position.y;
ApplyGeneratorFunctions(Parameters, generatorLayers[TILE_AIR], nextGeneratorLayers[TILE_AIR]);
ApplyGeneratorFunctions(Parameters, generatorLayers[TILE_FILLER], nextGeneratorLayers[TILE_FILLER]);
ApplyLiquids(Parameters, generatorLayers[TILE_LIQUID], nextGeneratorLayers[TILE_LIQUID]);
ApplyGeneratorFunctions(Parameters, generatorLayers[TILE_ORE], nextGeneratorLayers[TILE_ORE]);
ApplyGeneratorFunctions(Parameters, generatorLayers[TILE_NPC], nextGeneratorLayers[TILE_NPC]);
ApplyGeneratorFunctions(Parameters, generatorLayers[TILE_PLANT], nextGeneratorLayers[TILE_PLANT]);
}
Vector<ChunkGenerator::SpawnedEntities> ChunkGenerator::SpawnEntities(const std::vector<EntityTile> &persistantEntities) const
{
Vector<SpawnedEntities> spawnedEntities{};
Vector<Vector2i> claimedTilesBuffer{};
std::bitset<Chunk::ChunkSize * Chunk::ChunkSize> claimedTiles{};
auto chunkBounds = ChunkInfo.GetBounds();
ChunkCollection collection{};
FillChunkCollection(-1, +1, collection); FillChunkCollection(+0, +1, collection); FillChunkCollection(+1, +1, collection);
FillChunkCollection(-1, +0, collection); FillChunkCollection(+0, +0, collection); FillChunkCollection(+1, +0, collection);
FillChunkCollection(-1, -1, collection); FillChunkCollection(+0, -1, collection); FillChunkCollection(+1, -1, collection);
for (auto& entity : persistantEntities)
{
claimedTiles.set(entity.ChunkY * Chunk::ChunkSize + entity.ChunkX);
}
auto [layer, nextLayer] = GetLayers();
auto currentLayer = layer;
for (int y{chunkBounds.get_end().y}; y >= chunkBounds.get_position().y; --y)
{
if (y < chunkBounds.get_center().y && nextLayer.is_valid()) currentLayer = nextLayer;
for (int x{chunkBounds.get_position().x}; x < chunkBounds.get_end().x; ++x)
{
for (auto& arch : currentLayer->SpawningArchetypes)
{
for (auto& condition : arch->SpawnConditions)
{
if (!condition->IsValid(collection, Vector2i{x, y}))
{
goto NextArch;
}
}
for (auto& condition : arch->SpawnConditions)
{
claimedTilesBuffer.clear();
condition->ClaimTiles(claimedTilesBuffer, collection, Vector2i{x, y});
for (auto tile : claimedTilesBuffer)
{
if (!chunkBounds.has_point(Vector2i{x, y}) ||
claimedTiles[(chunkBounds.position.y - tile.y) * Chunk::ChunkSize + (chunkBounds.position.x - tile.x)])
{
goto NextArch;
}
}
for (auto tile : claimedTilesBuffer)
{
claimedTiles.set((chunkBounds.position.y - tile.y) * Chunk::ChunkSize + (chunkBounds.position.x - tile.x));
}
ChunkGenerator::SpawnedEntities spawnInfo{};
spawnInfo.Archetype = arch;
spawnInfo.SpawnPosition = Vector2i{x, y};
spawnInfo.ClaimedPositions = claimedTilesBuffer;
spawnedEntities.push_back(spawnInfo);
}
NextArch:;
}
}
}
}
std::unique_ptr<ChunkGenerator::CreatedVisualsChunk> ChunkGenerator::CreateVisuals()
{
auto visuals = std::make_unique<ChunkGenerator::CreatedVisualsChunk>();
for (int y{}; y < Chunk::ChunkSize; ++y)
{
for (int x{}; x < Chunk::ChunkSize; ++x)
{
auto tile = GetTile(x, y);
auto& tileConfig = Settings->TileConfigs[tile.GetID()];
auto& possibleTextures = tileConfig->PossibleTextures;
auto& possibleNeighbors = tileConfig->NeighborTransitions;
if (possibleTextures.size() == 1)
{
(*visuals)[y * Chunk::ChunkSize + x] = CreatedVisualsTile{ possibleTextures[0]->AtlasX, possibleTextures[0]->AtlasY, possibleTextures[0]->AtlasIndex };
}
else if (possibleTextures.size() > 1)
{
TextureWeight::GetTexture(possibleTextures, fastnoiselitestatic::SingleValue(Seed, x, y));
}
}
}
return visuals;
}
void ChunkGenerator::CascadeShadows_Recursive(std::array<int8_t, WorldNodeParameters::PaddedChunkSize>& values, int posX, int posY, int value)
{
if (InBounds(posX, posY))
{
int index = posY * WorldNodeParameters::PaddedChunkSide + posX;
value += Settings->TileConfigs[(*TileArray)[index].GetID()]->LightResistance;
if (value < values[index])
{
values[index] = value;
CascadeShadows_Recursive(values, posX + 1, posY, value);
CascadeShadows_Recursive(values, posX - 1, posY, value);
CascadeShadows_Recursive(values, posX, posY + 1, value);
CascadeShadows_Recursive(values, posX, posY - 1, value);
}
}
}
std::unique_ptr<ChunkGenerator::ChunkShadowValues> ChunkGenerator::CascadeShadows()
{
auto lightValues = std::make_unique<std::array<int8_t, WorldNodeParameters::PaddedChunkSize>>();
auto lightValuesChunk = std::make_unique<ChunkShadowValues>();
for (int i{}; i < WorldNodeParameters::PaddedChunkSize; ++i)
{
auto tile = (*TileArray)[i];
auto tileConfig = Settings->TileConfigs[tile.GetID()];
(*lightValues)[i] = tileConfig->LightResistance <= 0 ? tileConfig->LightResistance : std::numeric_limits<int8_t>::max();
}
for (int y{}; y < Chunk::ChunkSize; ++y)
{
for (int x{}; x < Chunk::ChunkSize; ++x)
{
CascadeShadows_Recursive(*lightValues, x, y, );
}
}
for (int y{}; y < Chunk::ChunkSize; ++y)
{
for (int x{}; x < Chunk::ChunkSize; ++x)
{
(*lightValuesChunk)[y * Chunk::ChunkSize + x] = lightValues[(y + WorldNodeParameters::MaxQueryOffset) * WorldNodeParameters::PaddedChunkSide + (x + WorldNodeParameters::MaxQueryOffset)];
}
}
return std::move(lightValuesChunk);
}

View File

@@ -0,0 +1,36 @@
#include "Core/WorldInstance.h"
#include "Types/Item.hpp"
#include "Components/Misc.hpp"
#include "Components/Resource.hpp"
#include "Components/Inventory.hpp"
#include "Components/Tick.hpp"
#include "Components/Chute.hpp"
#include "Components/Support.h"
WorldInstance::WorldInstance(const WorldConfig& worldConfig)
{
RegisterTypes(EcsWorld);
WorldInventory newInventory = WorldInventory{worldConfig.GetItems().size()};
EcsWorld.set<WorldConfig>(worldConfig);
EcsWorld.set<WorldInventory>(newInventory);
}
void WorldInstance::RegisterTypes(flecs::world &world)
{
Flecs_Misc(world);
Flecs_Item(world);
Flecs_Configs(world);
Flecs_Tick(world);
Flecs_Inventory(world);
Flecs_Resource(world);
Flecs_Chute(world);
Flecs_Support(world);
}
void WorldInstance::ProcessFrame()
{
EcsWorld.progress();
}

177
src/Data/Archetype.cpp Normal file
View File

@@ -0,0 +1,177 @@
#include "Data/Archetype.h"
#include "Core/Chunk.h"
// using namespace godot;
// #define MAKE_RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type)
// void Archetype::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_scene"), &Archetype::GetScene);
// ClassDB::bind_method(D_METHOD("get_preview_scene"), &Archetype::GetPreviewScene);
// ClassDB::bind_method(D_METHOD("get_preview_texture"), &Archetype::GetPreviewTexture);
// ClassDB::bind_method(D_METHOD("get_occupied_tiles"), &Archetype::GetOccupiedTiles);
// ClassDB::bind_method(D_METHOD("get_upgrades"), &Archetype::GetUpgrades);
// ClassDB::bind_method(D_METHOD("set_scene", "scene"), &Archetype::SetScene);
// ClassDB::bind_method(D_METHOD("set_preview_scene", "scene"), &Archetype::SetPreviewScene);
// ClassDB::bind_method(D_METHOD("set_preview_texture", "texture"), &Archetype::SetPreviewTetxture);
// ClassDB::bind_method(D_METHOD("set_occupied_tiles", "tiles"), &Archetype::SetOccupiedTiles);
// ClassDB::bind_method(D_METHOD("set_upgrades", "tiles"), &Archetype::SetUpgrades);
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scene", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), "set_scene", "get_scene");
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "preview_scene", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), "set_preview_scene", "get_preview_scene");
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "preview_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_preview_texture", "get_preview_texture");
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "occupied_tiles", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("OccupiedTile")), "set_occupied_tiles", "get_occupied_tiles");
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "upgrades", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("UpgradeLevelConfig")), "set_upgrades", "get_upgrades");
// }
// void SpawnDescription::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_filter"), &SpawnDescription::GetFilter);
// ClassDB::bind_method(D_METHOD("get_tile"), &SpawnDescription::GetTile);
// ClassDB::bind_method(D_METHOD("set_filter", "offset"), &SpawnDescription::SetFilter);
// ClassDB::bind_method(D_METHOD("set_tile", "tile"), &SpawnDescription::SetTile);
// ADD_PROPERTY(PropertyInfo(Variant::INT, "filter", PROPERTY_HINT_ENUM, TileTypeEnumString), "set_offset", "get_offset");
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "required_tile", PROPERTY_HINT_RESOURCE_TYPE, "TileConfig"), "set_tile", "get_tile");
// }
bool SpawnDescription::TryFilterTile(const ChunkCollection &chunks, int x, int y)
{
auto tile = chunks.TryGetTile(x, y);
if (tile) return FilterTile(*tile);
return false;
}
bool SpawnDescription::TryFilterTile(const ChunkCollection &chunks, Vector2i pos)
{
return TryFilterTile(chunks, pos.x, pos.y);
}
// void SpawnNearby::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_range"), &SpawnNearby::GetRange);
// ClassDB::bind_method(D_METHOD("set_range", "range"), &SpawnNearby::SetRange);
// ADD_PROPERTY(PropertyInfo(Variant::INT, "range"), "set_range", "get_range");
// }
bool SpawnNearby::IsValid(const ChunkCollection &chunk, Vector2i pos)
{
for (int y{pos.y - Range}; y < pos.y + Range; ++y)
{
for (int x{pos.x - Range}; x < pos.x + Range; ++x)
{
if (TryFilterTile(chunk, x, y))
return true;
}
}
return false;
}
// void OccupiedTiles::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_offsets"), &OccupiedTiles::GetOffset);
// ClassDB::bind_method(D_METHOD("set_offsets", "offsets"), &OccupiedTiles::SetOffset);
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "offsets", PROPERTY_HINT_ARRAY_TYPE, "Vector2i"), "set_offsets", "get_offsets");
// }
bool InBounds(Vector2i pos)
{
return pos.x >= 0 && pos.x < Chunk::ChunkSize && pos.y >= 0 && pos.y < Chunk::ChunkSize;
}
bool OccupiedTiles::IsValid(const ChunkCollection &chunk, Vector2i pos)
{
for (auto offset : Offsets)
{
if (!InBounds(pos + offset) || !TryFilterTile(chunk, pos + offset))
return false;
}
return true;
}
void OccupiedTiles::ClaimTiles(Vector<Vector2i> &tiles, const ChunkCollection &chunk, Vector2i pos)
{
for (auto offset : Offsets)
{
if (!tiles.has(pos + offset))
tiles.push_back(pos + offset);
}
}
void RequiredTiles::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_offsets"), &RequiredTiles::GetOffset);
ClassDB::bind_method(D_METHOD("set_offsets", "offsets"), &RequiredTiles::SetOffset);
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "offsets", PROPERTY_HINT_ARRAY_TYPE, "Vector2i"), "set_offsets", "get_offsets");
}
bool RequiredTiles::IsValid(const ChunkCollection &chunk, Vector2i pos)
{
for (auto offset : Offsets)
{
if (InBounds(pos + offset) && TryFilterTile(chunk, pos + offset))
return true;
}
return false;
}
void LinkedTiles::_bind_methods()
{
}
bool LinkedTiles::IsValid(const ChunkCollection &chunk, Vector2i pos)
{
return true;
}
void LinkedTiles::ClaimTiles_Recursive(Vector<Vector2i>& tiles, const ChunkCollection& chunk, Vector2i pos)
{
if (InBounds(pos) && TryFilterTile(chunk, pos) && !tiles.has(pos))
{
tiles.push_back(pos);
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(+1, -1));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(+1, +0));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(+1, +1));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(-1, -1));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(-1, +0));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(-1, +1));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(+0, +1));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(+0, -1));
}
}
void LinkedTiles::ClaimTiles(Vector<Vector2i> &tiles, const ChunkCollection &chunk, Vector2i pos)
{
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(+1, -1));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(+1, +0));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(+1, +1));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(-1, -1));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(-1, +0));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(-1, +1));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(+0, +1));
ClaimTiles_Recursive(tiles, chunk, pos + Vector2i(+0, -1));
}
void PlaceableArchetype::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_archetype"), &PlaceableArchetype::GetArchetype);
ClassDB::bind_method(D_METHOD("get_place_costs"), &PlaceableArchetype::GetPlaceCosts);
ClassDB::bind_method(D_METHOD("set_archetypes", "archetypes"), &PlaceableArchetype::SetArchetype);
ClassDB::bind_method(D_METHOD("set_place_costs", "costs"), &PlaceableArchetype::SetPlaceCosts);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "archetype", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), "set_archetype", "get_archetype");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "costs", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("ItemAmountConfig")), "set_place_costs", "get_place_costs");
}

49
src/Data/Item.cpp Normal file
View File

@@ -0,0 +1,49 @@
// #include "Data/Item.h"
// #include "Main/factory_server.h"
// #define MAKE_RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type)
// using namespace godot;
// void ItemConfig::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_id"), &ItemConfig::GetID);
// ClassDB::bind_method(D_METHOD("get_visual_name"), &ItemConfig::GetName);
// // ClassDB::bind_method(D_METHOD("is_fuel"), &ItemConfig::IsFuel);
// // ClassDB::bind_method(D_METHOD("is_fluid"), &ItemConfig::IsFluid);
// // ClassDB::bind_method(D_METHOD("is_ore"), &ItemConfig::IsOre);
// // ClassDB::bind_method(D_METHOD("is_plant"), &ItemConfig::IsPlant);
// // ClassDB::bind_method(D_METHOD("is_mob_drop"), &ItemConfig::IsMobDrop);
// // ClassDB::bind_method(D_METHOD("is_processed"), &ItemConfig::IsProcessed);
// ClassDB::bind_method(D_METHOD("set_visual_name", "name"), &ItemConfig::SetName);
// // ClassDB::bind_method(D_METHOD("set_fuel", "val"), &ItemConfig::SetFuel);
// // ClassDB::bind_method(D_METHOD("set_fluid", "val"), &ItemConfig::SetFluid);
// // ClassDB::bind_method(D_METHOD("set_ore", "val"), &ItemConfig::SetOre);
// // ClassDB::bind_method(D_METHOD("set_plant", "val"), &ItemConfig::SetPlant);
// // ClassDB::bind_method(D_METHOD("set_mob_drop", "val"), &ItemConfig::SetMobDrop);
// // ClassDB::bind_method(D_METHOD("set_processed", "val"), &ItemConfig::SetProcessed);
// ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "visual_name"), "set_visual_name", "get_visual_name");
// // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_fuel"), "set_fuel", "is_fuel");
// // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_fluid"), "set_fluid", "is_fluid");
// // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_ore"), "set_ore", "is_ore");
// // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_plant"), "set_plant", "is_plant");
// // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_mob_drop"), "set_mob_drop", "is_mob_drop");
// // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_processed"), "set_processed", "is_processed");
// }
// void ItemAmountConfig::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_item"), &ItemAmountConfig::GetItem);
// ClassDB::bind_method(D_METHOD("get_amount"), &ItemAmountConfig::GetAmount);
// ClassDB::bind_method(D_METHOD("set_item", "item"), &ItemAmountConfig::SetItem);
// ClassDB::bind_method(D_METHOD("set_amount", "amount"), &ItemAmountConfig::SetAmount);
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "ItemConfig"), "set_item", "get_item");
// ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amount"), "set_amount", "get_amount");
// }

51
src/Data/Recipe.cpp Normal file
View File

@@ -0,0 +1,51 @@
#include "Data/Recipe.h"
#include "Main/factory_server.h"
// #define MAKE_RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type)
// using namespace godot;
// void RecipeEntryConfig::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_item"), &RecipeEntryConfig::GetItem);
// ClassDB::bind_method(D_METHOD("get_amount"), &RecipeEntryConfig::GetAmount);
// ClassDB::bind_method(D_METHOD("set_item", "item"), &RecipeEntryConfig::SetItem);
// ClassDB::bind_method(D_METHOD("set_amount", "amount"), &RecipeEntryConfig::SetAmount);
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "ItemConfig"), "set_item", "get_item");
// ADD_PROPERTY(PropertyInfo(Variant::INT, "amount"), "set_amount", "get_amount");
// }
// void RecipeConfig::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_ingredients"), &RecipeConfig::GetIngredients);
// ClassDB::bind_method(D_METHOD("get_results"), &RecipeConfig::GetResults);
// ClassDB::bind_method(D_METHOD("get_processing_time"), &RecipeConfig::GetProcessingTime);
// ClassDB::bind_method(D_METHOD("set_ingredients", "item"), &RecipeConfig::SetIngredients);
// ClassDB::bind_method(D_METHOD("set_results", "amount"), &RecipeConfig::SetResults);
// ClassDB::bind_method(D_METHOD("set_processing_time", "time"), &RecipeConfig::SetProcessingTime);
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ingredients", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("RecipeEntryConfig")), "set_ingredients", "get_ingredients");
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "results", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("RecipeEntryConfig")), "set_results", "get_results");
// ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "processing_time"), "set_processing_time", "get_processing_time");
// }
// Recipe::Recipe(const Ref<RecipeConfig>& recipe)
// {
// if (recipe.is_null()) return;
// RecipeMeta meta{};
// meta.IngredientsAmount = recipe->Ingredients.size();
// meta.ResultsAmount = recipe->Results.size();
// meta.OriginalRecipe = recipe;
// meta.ProcessingTime = FactoryServer::SecondToTicks(recipe->ProcessingTime);
// Data = SharedBuffer<ItemAmount, RecipeMeta>(meta.IngredientsAmount + meta.ResultsAmount, meta);
// for (uint8_t i{}; i < meta.IngredientsAmount; ++i)
// Data[i] = recipe->Ingredients[i]->GetItemAmount();
// for (uint8_t i{}; i < meta.ResultsAmount; ++i)
// Data[i + meta.IngredientsAmount] = recipe->Results[i]->GetItemAmount();
// }

50
src/Data/Tile.cpp Normal file
View File

@@ -0,0 +1,50 @@
// #include "Data/Tile.h"
// #define MAKE_RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type)
// using namespace godot;
// void TileConfig::_bind_methods()
// {
// BIND_ENUM_CONSTANT(TILE_AIR);
// BIND_ENUM_CONSTANT(TILE_FILLER);
// BIND_ENUM_CONSTANT(TILE_LIQUID);
// BIND_ENUM_CONSTANT(TILE_ORE);
// BIND_ENUM_CONSTANT(TILE_PLANT);
// BIND_ENUM_CONSTANT(TILE_NPC);
// ClassDB::bind_method(D_METHOD("get_textures"), &TileConfig::GetTextures);
// ClassDB::bind_method(D_METHOD("get_transitions"), &TileConfig::GetTransitions);
// ClassDB::bind_method(D_METHOD("get_type"), &TileConfig::GetType);
// ClassDB::bind_method(D_METHOD("set_textures", "textures"), &TileConfig::SetTextures);
// ClassDB::bind_method(D_METHOD("set_transitions", "transitions"), &TileConfig::SetTransitions);
// ClassDB::bind_method(D_METHOD("set_type", "type"), &TileConfig::SetType);
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("TextureWeight")), "set_textures", "get_textures");
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("TileTransitionConfig")), "set_transitions", "get_transitions");
// ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, "Air,Filler,Liquid,Ore,Npc,Plant"), "set_type", "get_type");
// }
// void TileTransitionConfig::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_neighbor"), &TileTransitionConfig::GetNeighbor);
// ClassDB::bind_method(D_METHOD("get_possible_textures"), &TileTransitionConfig::GetPossibleTextures);
// ClassDB::bind_method(D_METHOD("set_neighbor", "neighbor"), &TileTransitionConfig::SetNeighbor);
// ClassDB::bind_method(D_METHOD("set_possible_textures", "textures"), &TileTransitionConfig::SetPossibleTextures);
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "neighbor", PROPERTY_HINT_RESOURCE_TYPE, "TileConfig"), "set_neighbor", "get_neighbor");
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "possible_textures", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("TextureWeight")), "set_possible_textures", "get_possible_textures");
// }
// void TextureWeight::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_texture"), &TextureWeight::GetTexture);
// ClassDB::bind_method(D_METHOD("get_weight"), &TextureWeight::GetWeight);
// ClassDB::bind_method(D_METHOD("set_texture", "texture"), &TextureWeight::SetTexture);
// ClassDB::bind_method(D_METHOD("set_weight", "weight"), &TextureWeight::SetWeight);
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
// ADD_PROPERTY(PropertyInfo(Variant::INT, "weight"), "set_weight", "get_weight");
// }

17
src/Data/UpgradeLevel.cpp Normal file
View File

@@ -0,0 +1,17 @@
// #include "Data/UpgradeLevel.h"
// #define MAKE_RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type)
// using namespace godot;
// void UpgradeLevelConfig::_bind_methods()
// {
// ClassDB::bind_method(D_METHOD("get_costs"), &UpgradeLevelConfig::GetUpgradeCosts);
// ClassDB::bind_method(D_METHOD("get_results"), &UpgradeLevelConfig::GetUpgradeResults);
// ClassDB::bind_method(D_METHOD("set_costs", "costs"), &UpgradeLevelConfig::SetUpgradeCosts);
// ClassDB::bind_method(D_METHOD("set_results", "results"), &UpgradeLevelConfig::SetUpgradeResults);
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "recipe_cost", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("ItemAmountConfig")), "set_costs", "get_costs");
// ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "results"), "set_results", "get_results");
// }

View File

@@ -0,0 +1,139 @@
#include "Data/WorldGraph/WorldGraph.h"
WorldGraph::WorldGraph(const Vector<Ref<WorldGraphVisualNodeBase>>& nodes)
{
Compile(nodes);
}
WorldGraph::WorldGraph(const WorldGraph &other)
{
MemorySize = other.MemorySize;
CompiledData = other.CopyMemory(NodeMap);
}
WorldGraph &WorldGraph::operator=(const WorldGraph &other)
{
MemorySize = other.MemorySize;
CompiledData = other.CopyMemory(NodeMap);
return *this;
}
Variant WorldGraph::Execute(Ref<WorldGraphVisualNodeBase> node, const WorldNodeParameters &params) const
{
auto nodePtr = GetNode(node);
if (nodePtr)
{
return nodePtr->Evaluate(params);
}
return {};
}
WorldNodeBase *WorldGraph::GetNode(Ref<WorldGraphVisualNodeBase> node) const
{
auto it = NodeMap.find(node);
if (it != NodeMap.end())
{
return it->value;
}
return nullptr;
}
void AddAllNodes(HashSet<Ref<WorldGraphVisualNodeBase>>& allNodes, Ref<WorldGraphVisualNodeBase> node)
{
allNodes.insert(node);
for (auto& input : node->InputNodes)
{
if (input.is_valid())
{
AddAllNodes(allNodes, input);
}
}
}
void WorldGraph::Compile(const Vector<Ref<WorldGraphVisualNodeBase>>& nodes)
{
NodeMap.clear();
HashSet<Ref<WorldGraphVisualNodeBase>> allNodes{};
HashMap<Ref<WorldGraphVisualNodeBase>, int> nodeMap{};
HashMap<WorldNodeBase*, WorldNodeBase*> linker{};
// collect all nodes
for (auto& node : nodes)
AddAllNodes(allNodes, node);
WorldGraphSizeMeasurer sizeMeasurer{};
for (auto& node : allNodes)
{
if (node.is_null() || !node->GetInternalNode())
throw std::runtime_error("graph is invalid");
// refresh node values
node->RefreshInputs();
node->RefreshValues();
// check if nodes are valid
if (!node->IsValid())
throw std::runtime_error("graph is invalid");
// find the size of the total compiled program
node->GetInternalNode()->Allocate(&sizeMeasurer);
}
DEV_ASSERT(sizeMeasurer.TotalSize % 8 == 0);
// allocate the nodes
WorldGraphAllocator allocator{ sizeMeasurer.TotalSize };
for (auto& node : allNodes)
{
void* allocatedNodeAddress = allocator.GetCurrentAddress();
node->GetInternalNode()->Allocate(&allocator);
NodeMap.insert(node, static_cast<WorldNodeBase*>(allocatedNodeAddress));
linker.insert(node->GetInternalNode(), static_cast<WorldNodeBase*>(allocatedNodeAddress));
}
// get the compiled memory
std::unique_ptr<WorldNodeBase*[]> compiledMemory = std::move(reinterpret_cast<std::unique_ptr<WorldNodeBase*[]>&>(allocator.Data));
// link the nodes
MemorySize = sizeMeasurer.TotalSize / 8;
for (int i{}; i < MemorySize; ++i)
{
auto it = linker.find(compiledMemory[i]);
if (it != linker.end())
{
compiledMemory[i] = it->value;
}
}
CompiledData = std::move(compiledMemory);
}
std::unique_ptr<WorldNodeBase *[]> WorldGraph::CopyMemory(HashMap<Ref<WorldGraphVisualNodeBase>, WorldNodeBase *> &nodeMap) const
{
auto memory = std::make_unique<WorldNodeBase*[]>(MemorySize);
int64_t memoryDifference = memory.get() - CompiledData.get();
nodeMap = NodeMap;
for (auto& node : nodeMap)
{
node.value += memoryDifference;
}
WorldNodeBase* memoryStart = reinterpret_cast<WorldNodeBase*>(CompiledData.get());
WorldNodeBase* memoryEnd = reinterpret_cast<WorldNodeBase*>(CompiledData.get() + MemorySize);
for (int i{}; i < MemorySize; ++i)
{
auto& address = memory[i];
// if the address points somewhere that is in the old memory address, move it to the new memory address
if (address >= memoryStart && address < memoryEnd)
{
address += memoryDifference;
}
}
return std::move(memory);
}

View File

@@ -0,0 +1,338 @@
#include "Data/WorldGraph/WorldGraphVisualNode.h"
void WorldGraphVisualNodeBase::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_position"), &WorldGraphVisualNodeBase::GetPosition);
ClassDB::bind_method(D_METHOD("get_inputs"), &WorldGraphVisualNodeBase::GetInputs);
ClassDB::bind_method(D_METHOD("set_position", "position"), &WorldGraphVisualNodeBase::SetPosition);
ClassDB::bind_method(D_METHOD("set_inputs", "input"), &WorldGraphVisualNodeBase::SetInputNodes);
ClassDB::bind_method(D_METHOD("is_valid"), &WorldGraphVisualNodeBase::NodeIsValid);
ClassDB::bind_method(D_METHOD("get_input_types"), &WorldGraphVisualNodeBase::NodeGetInputTypes);
ClassDB::bind_method(D_METHOD("get_output_type"), &WorldGraphVisualNodeBase::NodeGetOutputType);
//ClassDB::bind_method(D_METHOD("set_default"), &WorldGraphVisualNodeBase::NodeSetDefault);
ClassDB::bind_method(D_METHOD("has_internal_node"), &WorldGraphVisualNodeBase::HasInternalNode);
ClassDB::bind_method(D_METHOD("set_input", "index", "node"), &WorldGraphVisualNodeBase::NodeSetInput);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "position"), "set_position", "get_position");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "inputs", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("WorldGraphVisualNodeBase")), "set_inputs", "get_inputs");
}
bool CanExecuteNode_Recursive(Ref<WorldGraphVisualNodeBase> node)
{
for (auto& node : node->InputNodes)
{
for (auto input : node->InputNodes)
{
if (input.is_null()) return false;
CanExecuteNode_Recursive(input);
}
}
return true;
}
bool WorldGraphVisualNodeBase::CanExecuteNode()
{
return CanExecuteNode_Recursive(Ref(this));
}
void WorldGraphVisualNodeBase::SetInternalNode(std::unique_ptr<WorldNodeBase> &&node)
{
InternalNode = std::move(node);
int inputAmounts = InternalNode->GetInputTypes().size();
if (inputAmounts > InputNodes.size())
{
InputNodes.resize(inputAmounts);
}
RefreshInputs();
RefreshValues();
}
void WorldGraphVisualNodeBase::RefreshInputs()
{
if (InternalNode)
{
auto internalNodeInputs = InternalNode->GetInputTypes().size();
for (int i{}; i < InputNodes.size() && i < internalNodeInputs; ++i)
SetInput(i, InputNodes[i]);
}
}
struct NodeGenerator
{
NodeGenerator() = default;
NodeGenerator(const char* name, std::function<std::unique_ptr<WorldNodeBase>()>&& generator) : Name{ name }, Generator{ std::move(generator) } {};
const char* Name{};
std::function<std::unique_ptr<WorldNodeBase>()> Generator{};
};
static const NodeGenerator MathGenerators[] =
{
{ "Abs", [](){ return std::make_unique<WorldNode_Abs>(); }},
{ "Add", [](){ return std::make_unique<WorldNode_Add>(); }},
{ "And", [](){ return std::make_unique<WorldNode_And>(); }},
{ "Ceil", [](){ return std::make_unique<WorldNode_Ceil>(); }},
{ "Clamp", [](){ return std::make_unique<WorldNode_Clamp>(); }},
{ "Cos", [](){ return std::make_unique<WorldNode_Cos>(); }},
{ "Divide", [](){ return std::make_unique<WorldNode_Divide>(); }},
{ "Equal", [](){ return std::make_unique<WorldNode_Equal>(); }},
{ "Exp", [](){ return std::make_unique<WorldNode_Exp>(); }},
{ "Floor", [](){ return std::make_unique<WorldNode_Floor>(); }},
{ "Greater", [](){ return std::make_unique<WorldNode_Greater>(); }},
{ "Greater or Equal", [](){ return std::make_unique<WorldNode_GreaterEqual>(); }},
{ "Lerp", [](){ return std::make_unique<WorldNode_Lerp>(); }},
{ "Log", [](){ return std::make_unique<WorldNode_Log>(); }},
{ "Max", [](){ return std::make_unique<WorldNode_Max>(); }},
{ "Min", [](){ return std::make_unique<WorldNode_Min>(); }},
{ "Modulo", [](){ return std::make_unique<WorldNode_Modulo>(); }},
{ "Multiply", [](){ return std::make_unique<WorldNode_Multiply>(); }},
{ "Negate", [](){ return std::make_unique<WorldNode_Negate>(); }},
{ "One Minus", [](){ return std::make_unique<WorldNode_OneMinus>(); }},
{ "Or", [](){ return std::make_unique<WorldNode_Or>(); }},
{ "Pow", [](){ return std::make_unique<WorldNode_Pow>(); }},
{ "Round", [](){ return std::make_unique<WorldNode_Round>(); }},
{ "Sin", [](){ return std::make_unique<WorldNode_Sin>(); }},
{ "Smaller", [](){ return std::make_unique<WorldNode_Smaller>(); }},
{ "Smaller or Equal", [](){ return std::make_unique<WorldNode_SmallerEqual>(); }},
{ "Square", [](){ return std::make_unique<WorldNode_Square>(); }},
{ "Substract", [](){ return std::make_unique<WorldNode_Subtract>(); }},
{ "Tan", [](){ return std::make_unique<WorldNode_Tan>(); }},
};
static constexpr int MathArraySize = sizeof(MathGenerators) / sizeof(NodeGenerator);
void WorldGraphVisualNode_Math::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_node"), &WorldGraphVisualNode_Math::GetNode);
ClassDB::bind_method(D_METHOD("get_node_names"), &WorldGraphVisualNode_Math::GetNodeNames);
ClassDB::bind_method(D_METHOD("set_node", "name"), &WorldGraphVisualNode_Math::SetNode);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "node_id"), "set_node", "get_node");
}
WorldGraphVisualNode_Math::WorldGraphVisualNode_Math()
{
NodeID = "Add";
SetInternalNode(std::make_unique<WorldNode_Add>());
}
TypedArray<String> WorldGraphVisualNode_Math::GetNodeNames() const
{
TypedArray<String> Values{};
Values.resize(MathArraySize);
for (int i{}; i < MathArraySize; ++i)
{
Values[i] = String(MathGenerators[i].Name);
}
return Values;
}
void WorldGraphVisualNode_Math::SetNode(String nodeName)
{
if (nodeName == NodeID) return;
for (int i{}; i < MathArraySize; ++i)
{
if (MathGenerators[i].Name == nodeName)
{
SetInternalNode(MathGenerators[i].Generator());
NodeID = nodeName;
}
}
}
void WorldGraphVisualNode_Constant::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_value"), &WorldGraphVisualNode_Constant::GetValue);
ClassDB::bind_method(D_METHOD("set_value", "val"), &WorldGraphVisualNode_Constant::SetValue);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "value"), "set_value", "get_value");
}
WorldGraphVisualNode_Constant::WorldGraphVisualNode_Constant()
{
SetInternalNode(std::make_unique<WorldNode_Constant>());
}
void WorldGraphVisualNode_Constant::SetValue(float val)
{
static_cast<WorldNode_Constant*>(GetInternalNode())->Value = val;
}
float WorldGraphVisualNode_Constant::GetValue() const
{
return static_cast<WorldNode_Constant*>(GetInternalNode())->Value;
}
static const NodeGenerator NoiseGenerators[] =
{
{ "Simplex", [](){ return std::make_unique<WorldNode_Simplex>(); }},
{ "Open Simplex", [](){ return std::make_unique<WorldNode_OpenSimplex>(); }},
{ "Perlin", [](){ return std::make_unique<WorldNode_Perlin>(); }},
{ "Value", [](){ return std::make_unique<WorldNode_Value>(); }},
{ "Value Cubic", [](){ return std::make_unique<WorldNode_ValueCubic>(); }},
};
static constexpr int NoiseArraySize = sizeof(NoiseGenerators) / sizeof(NodeGenerator);
WorldGraphVisualNode_Noise::WorldGraphVisualNode_Noise()
{
NoiseType = "Simplex";
SetInternalNode(std::make_unique<WorldNode_Simplex>());
}
TypedArray<String> WorldGraphVisualNode_Noise::GetNoiseTypes() const
{
TypedArray<String> Values{};
Values.resize(NoiseArraySize);
for (int i{}; i < NoiseArraySize; ++i)
{
Values[i] = String(NoiseGenerators[i].Name);
}
return Values;
}
void WorldGraphVisualNode_Noise::SetFrequency(float val)
{
Frequency = val;
if (val != 0)
{
static_cast<WorldNode_NoiseBase*>(GetInternalNode())->Frequency = val;
}
}
void WorldGraphVisualNode_Noise::RefreshValues()
{
static_cast<WorldNode_NoiseBase*>(GetInternalNode())->Frequency = Frequency;
}
void WorldGraphVisualNode_Noise::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_noise_type"), &WorldGraphVisualNode_Noise::GetNoiseType);
ClassDB::bind_method(D_METHOD("get_all_noise_types"), &WorldGraphVisualNode_Noise::GetNoiseTypes);
ClassDB::bind_method(D_METHOD("get_frequency"), &WorldGraphVisualNode_Noise::GetFrequency);
ClassDB::bind_method(D_METHOD("set_noise_type", "type"), &WorldGraphVisualNode_Noise::SetNoiseType);
ClassDB::bind_method(D_METHOD("set_frequency", "frequency"), &WorldGraphVisualNode_Noise::SetFrequency);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "noise_type"), "set_noise_type", "get_noise_type");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "frequency"), "set_frequency", "get_frequency");
}
void WorldGraphVisualNode_Noise::SetNoiseType(String noiseType) {
if (noiseType == NoiseType) return;
for (int i{}; i < NoiseArraySize; ++i)
{
if (NoiseGenerators[i].Name == noiseType)
{
SetInternalNode(NoiseGenerators[i].Generator());
NoiseType = noiseType;
}
}
}
// float WorldGraphVisualNode_Noise::GetFrequency() const
// {
// return static_cast<WorldNode_NoiseBase*>(GetInternalNode())->Frequency;
// }
WorldGraphVisualNode_If::WorldGraphVisualNode_If()
{
SetInternalNode(std::make_unique<WorldNode_Branch>());
}
void WorldGraphVisualNode_Tile::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_type"), &WorldGraphVisualNode_Tile::GetType);
ClassDB::bind_method(D_METHOD("get_relative_x"), &WorldGraphVisualNode_Tile::GetRelativeX);
ClassDB::bind_method(D_METHOD("get_relative_y"), &WorldGraphVisualNode_Tile::GetRelativeY);
ClassDB::bind_method(D_METHOD("set_type", "type"), &WorldGraphVisualNode_Tile::SetType);
ClassDB::bind_method(D_METHOD("set_relative_x", "offset"), &WorldGraphVisualNode_Tile::SetRelativeX);
ClassDB::bind_method(D_METHOD("set_relative_y", "offset"), &WorldGraphVisualNode_Tile::SetRelativeY);
ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, "Air,Filler,Liquid,Ore,Npc,Plant"), "set_type", "get_type");
ADD_PROPERTY(PropertyInfo(Variant::INT, "relative_x"), "set_relative_x", "get_relative_x");
ADD_PROPERTY(PropertyInfo(Variant::INT, "relative_y"), "set_relative_y", "get_relative_y");
}
WorldGraphVisualNode_Tile::WorldGraphVisualNode_Tile()
{
SetInternalNode(std::make_unique<WorldNode_IsTile>());
}
void WorldGraphVisualNode_Tile::SetType(int type)
{
static_cast<WorldNode_IsTile*>(GetInternalNode())->TileType = static_cast<TILE_TYPE>(std::clamp<int>(type, 0, TILE_TYPE::TILE_MAX));
}
void WorldGraphVisualNode_Tile::SetRelativeX(int offset)
{
static_cast<WorldNode_IsTile*>(GetInternalNode())->RelativeX = static_cast<int8_t>(std::clamp<int>(offset, -WorldNodeParameters::MaxQueryOffset, WorldNodeParameters::MaxQueryOffset));
}
void WorldGraphVisualNode_Tile::SetRelativeY(int offset)
{
static_cast<WorldNode_IsTile*>(GetInternalNode())->RelativeY = static_cast<int8_t>(std::clamp<int>(offset, -WorldNodeParameters::MaxQueryOffset, WorldNodeParameters::MaxQueryOffset));
}
int WorldGraphVisualNode_Tile::GetType() const
{
return static_cast<WorldNode_IsTile*>(GetInternalNode())->TileType;
}
int WorldGraphVisualNode_Tile::GetRelativeX() const
{
return static_cast<WorldNode_IsTile*>(GetInternalNode())->RelativeX;
}
int WorldGraphVisualNode_Tile::GetRelativeY() const
{
return static_cast<WorldNode_IsTile*>(GetInternalNode())->RelativeY;
}
void WorldGraphVisualNode_TileDistance::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_type"), &WorldGraphVisualNode_TileDistance::GetType);
ClassDB::bind_method(D_METHOD("get_range"), &WorldGraphVisualNode_TileDistance::GetRange);
ClassDB::bind_method(D_METHOD("set_type", "type"), &WorldGraphVisualNode_TileDistance::SetType);
ClassDB::bind_method(D_METHOD("set_range", "range"), &WorldGraphVisualNode_TileDistance::SetRange);
ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, "Air,Filler,Liquid,Ore,Npc,Plant"), "set_type", "get_type");
ADD_PROPERTY(PropertyInfo(Variant::INT, "range", PROPERTY_HINT_RANGE, "1,3"), "set_range", "get_range");
}
WorldGraphVisualNode_TileDistance::WorldGraphVisualNode_TileDistance()
{
SetInternalNode(std::make_unique<WorldNode_TileDistance>());
}
void WorldGraphVisualNode_TileDistance::SetType(int type)
{
static_cast<WorldNode_TileDistance*>(GetInternalNode())->TileType = static_cast<TILE_TYPE>(type);
}
void WorldGraphVisualNode_TileDistance::SetRange(int range)
{
static_cast<WorldNode_TileDistance*>(GetInternalNode())->Range = range;
}
int WorldGraphVisualNode_TileDistance::GetType() const
{
return static_cast<WorldNode_TileDistance*>(GetInternalNode())->TileType;
}
int WorldGraphVisualNode_TileDistance::GetRange() const
{
return static_cast<WorldNode_TileDistance*>(GetInternalNode())->Range;
}

652
src/Data/WorldSettings.cpp Normal file
View File

@@ -0,0 +1,652 @@
#include "Data/WorldSettings.h"
#include "Util/Helpers.h"
#include "core/io/resource_saver.h"
#include "core/config/project_settings.h"
#include "servers/rendering_server.h"
#include <functional>
#include "Core/Chunk.h"
#include "Data/LayerConfigs.h"
#include "Data/Item.h"
#include "Core/WorldGenerator.h"
#include "Util/RandomPicker.h"
// #define MAKE_RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type)
// using namespace godot;
// void FactoryWorldSettings::_bind_methods()
// {
// std::function test{ std::divides<float>() };
// ClassDB::bind_method(D_METHOD("get_recipes"), &FactoryWorldSettings::GetRecipes);
// ClassDB::bind_method(D_METHOD("get_archetypes"), &FactoryWorldSettings::GetPlaceableArchetypes);
// ClassDB::bind_method(D_METHOD("get_tiles"), &FactoryWorldSettings::GetTileConfigs);
// ClassDB::bind_method(D_METHOD("get_layers"), &FactoryWorldSettings::GetLayerConfigs);
// ClassDB::bind_method(D_METHOD("set_recipes", "recipes"), &FactoryWorldSettings::SetRecipes);
// ClassDB::bind_method(D_METHOD("set_archetypes", "archetypes"), &FactoryWorldSettings::SetPlaceableArchetypes);
// ClassDB::bind_method(D_METHOD("set_tiles", "tiles"), &FactoryWorldSettings::SetTileConfigs);
// ClassDB::bind_method(D_METHOD("set_layers", "tiles"), &FactoryWorldSettings::SetLayerConfigs);
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "recipes", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("RecipeConfig")), "set_recipes", "get_recipes");
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "archetypes", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("PlaceableArchetype")), "set_archetypes", "get_archetypes");
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "tiles", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("TileConfig")), "set_tiles", "get_tiles");
// ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "layers", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("LayerConfig")), "set_layers", "get_layers");
// }
template <typename T>
void AddUnique(Vector<T>& target, const Vector<T>& source)
{
for (auto& src : source)
{
for (auto& tar : target)
{
if (src == tar || src.is_null())
continue;
}
target.push_back(src);
}
}
void FactoryWorldSettings::Merge(Ref<FactoryWorldSettings> settings)
{
// merge layers
AddUnique(LayerConfigs, settings->LayerConfigs);
AddUnique(TileConfigs, settings->TileConfigs);
AddUnique(Recipes, settings->Recipes);
AddUnique(Archetypes, settings->Archetypes);
//AddUnique(Placeables, settings->Placeables);
AddUnique(Items, settings->Items);
Initialize();
}
void GetSubResources(Resource* resource, List<PropertyInfo>& buffer, const std::function<void(Resource*)>& callback);
bool CheckVariant(Variant var, List<PropertyInfo>& buffer, const std::function<void(Resource*)>& callback)
{
if (var.get_type() == Variant::Type::OBJECT)
{
auto resourceVar = Object::cast_to<Resource>(var);
if (resourceVar)
{
callback(resourceVar);
GetSubResources(resourceVar, buffer, callback);
return true;
}
}
return false;
}
void GetSubResources(Resource* resource, List<PropertyInfo>& buffer, const std::function<void(Resource*)>& callback)
{
resource->get_property_list(&buffer);
while (!buffer.is_empty())
{
auto property = buffer.front();
auto className = property->get().class_name;
StringName propertyName = property->get().name;
auto getterName = ClassDB::get_property_getter(className, propertyName);
auto propertyVal = resource->call(getterName);
if (CheckVariant(propertyVal, buffer, callback))
{
auto packedScene = Object::cast_to<PackedScene>(propertyVal);
if (packedScene)
{
auto state = packedScene->get_state();
for (int i{}; i < state->get_node_count(); ++i)
for (int j{}; j < state->get_node_property_count(i); ++j)
{
CheckVariant(state->get_node_property_value(i, j), buffer, callback);
}
}
if (propertyVal.is_array())
{
Array propertyArray = propertyVal;
for (auto entry : propertyArray)
{
CheckVariant(entry, buffer, callback);
}
}
}
buffer.pop_front();
}
}
void AddTextureToSheets(HashMap<int,Vector<Ref<TextureWeight>>> sheets, Ref<Texture2D> texture)
{
if (texture.is_null()) return;
DEV_ASSERT(texture->get_width() == texture->get_height());
if (!sheets.has(texture->get_width()))
{
// Ref<TileSetAtlasSource> tileSet{};
// tileSet.instantiate();
// tileSet->set_texture_region_size(texture->get_size());
// sheets[texture->get_width()] = tileSet;
sheets.insert(texture->get_width(), {});
}
auto& sheet = sheets[texture->get_width()];
sheet.push_back(texture);
}
void FactoryWorldSettings::Initialize()
{
InitializeResources();
InitializeLayers();
InitializeWorldGenerators();
InitializeTexturesSheets();
InitializeItemGraph();
}
int FactoryWorldSettings::GetStartHeight() const
{
return Chunk::ChunkSize * LayerConfigs[0]->StartChunk;
}
Vector<ItemAmount> FactoryWorldSettings::GetChunkUnlockCosts(int x, int y) const
{
return GetChunkUnlockCosts(ChunkKey{x, y});
}
constexpr double Square(double x)
{
return x * x;
}
constexpr double BellCurve(double x, double mean = 0, double deviation = 0.4)
{
constexpr double Sqrt2Pi = 2.506628274631000502415765284811;
return (1/(deviation * Sqrt2Pi)) * exp(-0.5 * Square((x - mean) / deviation));
}
double GetRandomDouble(uint64_t& val)
{
double randomVal{ reinterpret_cast<const double&>(val) };
while (isnan(randomVal) || std::isnormal(randomVal))
{
val = std::hash<uint64_t>{}(val);
randomVal = reinterpret_cast<const double&>(val);
}
return randomVal;
}
Vector<ItemAmount> FactoryWorldSettings::GetChunkUnlockCosts(ChunkKey chunk) const
{
HashMap<Ref<LayerConfig>,int> LayerCounts{};
for (int y{ LayerConfigs[0]->StartChunk }; y > chunk.Y; --y)
{
auto key = chunk;
key.Y = y;
++LayerCounts[GetLayer(key)];
}
for (int x{ }; x < std::abs(chunk.X); ++x)
{
auto key = chunk;
key.X = chunk.X < 0 ? -x : x;
++LayerCounts[GetLayer(key)];
}
std::vector<RandomPickerD<ItemConfig*>::Entry> possibleItems{};
possibleItems.push_back(RandomPickerD<ItemConfig*>::Entry{ nullptr, BellCurve(0, chunk.X + chunk.Y) });
possibleItems.push_back(RandomPickerD<ItemConfig*>::Entry{ nullptr, BellCurve(0, chunk.X + chunk.Y) });
for (auto [layer, count] : LayerCounts)
{
int maxComplexity{};
for (auto item : layer->UnlockedItems)
{
maxComplexity = std::max(maxComplexity, ItemComplexity[item]);
}
for (auto item : layer->UnlockedItems)
{
auto complexity = ItemComplexity[item];
possibleItems.push_back(RandomPickerD<ItemConfig*>::Entry{ item.ptr(), BellCurve(complexity, std::min(count / 3, maxComplexity)) * count });
}
}
RandomPickerD<ItemConfig*> itemPicker{ possibleItems };
uint64_t chunkHash = chunk.hash64();
return Vector<ItemAmount>
{
itemPicker.GetAndRemoveRandom(GetRandomDouble(chunkHash)),
itemPicker.GetAndRemoveRandom(GetRandomDouble(chunkHash)),
itemPicker.GetAndRemoveRandom(GetRandomDouble(chunkHash))
};
}
Ref<LayerConfig> FactoryWorldSettings::GetLayer(ChunkKey chunk) const
{
for (auto layer : LayerConfigs)
{
if (layer->StartChunk <= chunk.Y)
return layer;
}
}
void FactoryWorldSettings::InitializeResources()
{
List<PropertyInfo> buffer{};
struct MetaData
{
RecipeConfig* CurrentRecipe{};
Archetype* CurrentArchetype{};
LayerConfig* CurrentLayer{};
};
MetaData data{};
std::function<void(Resource*)> resourceChecker = [this, &data](Resource* res)
{
{
auto item = Object::cast_to<ItemConfig>(res);
if (item && item->GetID() == -1)
{
Items.push_back(item);
item->Item.ItemID = static_cast<uint16_t>(Items.size() - 1);
}
if (data.CurrentLayer)
{
data.CurrentLayer->UnlockedItems.push_back_unique(item);
}
}
{
auto tile = Object::cast_to<TileConfig>(res);
if (tile && !TileConfigs.has(tile))
{
TileConfigs.push_back(tile);
tile->TileData.SetID(static_cast<uint16_t>(TileConfigs.size() - 1));
}
}
{
auto recipe = Object::cast_to<RecipeConfig>(res);
if (recipe)
{
data.CurrentRecipe = recipe;
if (!Recipes.has(recipe))
{
Recipes.push_back(recipe);
recipe->SetID(Recipes.size() - 1);
}
if (data.CurrentArchetype)
{
recipe->RecipeSources.push_back_unique(data.CurrentArchetype);
}
if (data.CurrentLayer)
{
data.CurrentLayer->UnlockedRecipes.push_back_unique(recipe);
for (auto result : recipe->Results)
data.CurrentLayer->UnlockedItems.push_back_unique(result);
}
}
}
{
auto archetype = Object::cast_to<Archetype>(res);
if (archetype && !Archetypes.has(archetype))
{
data.CurrentArchetype = archetype;
data.CurrentRecipe = nullptr;
Archetypes.push_back(archetype);
archetype->SetID(Archetypes.size() - 1);
}
if (data.CurrentLayer)
{
data.CurrentLayer->UnlockedBuildings.push_back_unique(archetype);
}
}
{
auto tile = Object::cast_to<TileConfig>(res);
if (tile)
{
TileConfigs.push_back_unique(tile);
}
}
};
for (int i{}; i < LayerConfigs.size(); ++i)
{
data.CurrentLayer = LayerConfigs[i].ptr();
data.CurrentArchetype = nullptr;
data.CurrentRecipe = nullptr;
GetSubResources(LayerConfigs[i].ptr(), buffer, resourceChecker);
}
data.CurrentLayer = nullptr;
data.CurrentArchetype = nullptr;
data.CurrentRecipe = nullptr;
for (int i{}; i < PlaceableArchetypes.size(); ++i)
{
data.CurrentArchetype = PlaceableArchetypes[i]->Archetype.ptr();
GetSubResources(PlaceableArchetypes[i].ptr(), buffer, resourceChecker);
}
}
void FactoryWorldSettings::InitializeLayers()
{
// sort Layers
struct LayerSorter
{
bool operator()(const Ref<LayerConfig>& lhs, const Ref<LayerConfig>& rhs) const
{
return lhs->StartChunk > rhs->StartChunk;
}
};
LayerConfigs.sort_custom<LayerSorter>();
struct TileSorter
{
bool operator()(const Ref<LayerTileConfig>& lhs, const Ref<LayerTileConfig>& rhs) const
{
return lhs->Tile->GetType() < rhs->Tile->GetType();
}
};
for (auto& layer : LayerConfigs)
{
layer->Tiles.sort_custom<TileSorter>();
}
}
void FactoryWorldSettings::InitializeWorldGenerators()
{
// Compile World Generator
Vector<Ref<WorldGraphVisualNodeBase>> graphs;
for (auto& layer : LayerConfigs)
for (auto& tileGenerator : layer->Tiles)
if (!LayerConfigs.has(tileGenerator->TileGenerator))
LayerConfigs.push_back(tileGenerator->TileGenerator);
WorldGenerator = WorldGraph{ graphs };
}
void FactoryWorldSettings::InitializeTexturesSheets()
{
// Gather all textures
TileSet = {};
HashMap<int,Vector<Ref<TextureWeight>>> Sheets{};
for (auto tile : TileConfigs)
{
for (auto texture : tile->PossibleTextures)
AddTextureToSheets(Sheets, texture);
for (auto transition : tile->NeighborTransitions)
for (auto texture : transition->PossibleTextures)
AddTextureToSheets(Sheets, texture);
}
// Make sheets
for (auto& [size, textures] : Sheets)
{
constexpr int32_t MaxTextureSizeBits = 12; // 4096
const int32_t individualTextureSizeBits = (int32_t)std::ceil(std::log2(size));
const int32_t textureAmountBits = ((int32_t)std::ceil(std::log2(textures.size())) + 1) / 2;
const int32_t SheetsAmountBits = std::max(0, individualTextureSizeBits + textureAmountBits - MaxTextureSizeBits);
const int32_t SheetDimensionBits = std::max(0, individualTextureSizeBits + textureAmountBits);
const int32_t SheetsAmount = 1 << SheetsAmountBits;
const int32_t SheetDimensions = 1 << SheetDimensionBits;
const int32_t TexturesPerSheet = 1 << (textureAmountBits * 2);
const int32_t TexturesPerRow = 1 << textureAmountBits;
for (int sheetIndex{}; sheetIndex < SheetsAmount; ++sheetIndex)
{
Ref<Image> sheet{};
sheet.instantiate();
sheet->initialize_data(SheetDimensions, SheetDimensions, true, Image::FORMAT_RGBA8);
for (int i{sheetIndex * TexturesPerSheet}; i < textures.size() && i < (sheetIndex + 1) * TexturesPerSheet; ++i)
{
int x = textures[i]->AtlasX = i % TexturesPerRow;
int y = textures[i]->AtlasY = (i / TexturesPerRow) % TexturesPerRow;
textures[i]->AtlasIndex = TileSet->get_source_count();
auto texture = textures[i]->Texture;
Image sourceImage = Image{};
sheet->blit_rect(texture, Rect2i{Vector2i{}, texture->get_size()}, Vector2i{x, y});
}
Ref<TileSetAtlasSource> tileSetSource{};
tileSetSource.instantiate();
tileSetSource->set_texture_region_size(Vector2i{size, size});
tileSetSource->set_texture(ImageTexture::create_from_image(sheet));
TileSet->add_source(tileSetSource);
}
}
}
Ref<RecipeConfig> IsItemFromRecipe(Ref<LayerConfig> layer, Ref<ItemConfig> item)
{
for (auto recipe : layer->UnlockedRecipes)
{
for (auto result : recipe->Results)
{
if (result->Item == item)
{
return recipe;
}
}
}
return {};
}
void FactoryWorldSettings::InitializeItemGraph()
{
for (auto layer : LayerConfigs)
{
for (bool repeat{}; repeat;)
{
for (auto item : layer->UnlockedItems)
{
auto recipe = IsItemFromRecipe(layer, item);
if (recipe.is_valid())
{
int maxComplexity{};
for (auto ingredient : recipe->Ingredients)
{
if (ItemComplexity.has(ingredient))
{
maxComplexity = std::max(maxComplexity, ItemComplexity[ingredient]);
}
else
{
repeat = true;
}
}
ItemComplexity[item] = maxComplexity;
}
else
{
ItemComplexity[item] = 0;
}
}
}
}
}
#define MAKE_RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type)
void LayerConfig::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_start_chunk"), &LayerConfig::GetStartChunk);
ClassDB::bind_method(D_METHOD("get_tiles"), &LayerConfig::GetTiles);
ClassDB::bind_method(D_METHOD("get_registered_nodes"), &LayerConfig::GetRegisteredNodes);
ClassDB::bind_method(D_METHOD("get_archetypes"), &LayerConfig::GetArchetypes);
ClassDB::bind_method(D_METHOD("set_start_chunk", "height"), &LayerConfig::SetStartChunk);
ClassDB::bind_method(D_METHOD("set_tiles", "tiles"), &LayerConfig::SetTiles);
ClassDB::bind_method(D_METHOD("set_registered_nodes", "nodes"), &LayerConfig::SetRegisteredNodes);
ClassDB::bind_method(D_METHOD("set_archetypes", "archetypes"), &LayerConfig::SetArchetypes);
ClassDB::bind_method(D_METHOD("register_visual_node", "node"), &LayerConfig::RegisterVisualNode);
ClassDB::bind_method(D_METHOD("remove_visual_node", "node"), &LayerConfig::RemoveVisualNode);
ClassDB::bind_method(D_METHOD("has_visual_node", "node"), &LayerConfig::HasVisualNode);
ClassDB::bind_method(D_METHOD("is_valid"), &LayerConfig::IsValid);
ClassDB::bind_method(D_METHOD("can_connect", "connect"), &LayerConfig::CanConnect);
ClassDB::bind_method(D_METHOD("create_texture", "chunk", "seed"), &LayerConfig::CreateTexture);
ADD_PROPERTY(PropertyInfo(Variant::INT, "start_chunk"), "set_start_chunk", "get_start_chunk");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "tile", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("LayerTileConfig")), "set_tiles", "get_tiles");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "registered_nodes", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("WorldGraphVisualNodeBase")), "set_registered_nodes", "get_registered_nodes");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "spawning_archetypes", PROPERTY_HINT_TYPE_STRING, MAKE_RESOURCE_TYPE_HINT("Archetype")), "set_archetypes", "get_archetypes");
}
void LayerConfig::SetTiles(TypedArray<LayerTileConfig> tiles)
{
Tiles = TypedArrayToVector(tiles);
struct TileSorter
{
bool operator()(const Ref<LayerTileConfig>& lhs, const Ref<LayerTileConfig>& rhs) const
{
return lhs->Tile->TileData.GetType() < rhs->Tile->TileData.GetType();
}
};
for (auto& tile : Tiles)
{
if (tile.is_null() || tile->Tile.is_null()) return;
}
Tiles.sort_custom<TileSorter>();
}
void LayerTileConfig::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_generator"), &LayerTileConfig::GetTileGenerator);
ClassDB::bind_method(D_METHOD("get_tile"), &LayerTileConfig::GetTile);
ClassDB::bind_method(D_METHOD("get_preview_color"), &LayerTileConfig::GetPreviewColor);
ClassDB::bind_method(D_METHOD("set_generator", "generator"), &LayerTileConfig::SetTileGenerator);
ClassDB::bind_method(D_METHOD("set_tile", "tile"), &LayerTileConfig::SetTile);
ClassDB::bind_method(D_METHOD("set_preview_color", "color"), &LayerTileConfig::SetPreviewColor);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "generator", PROPERTY_HINT_RESOURCE_TYPE, "WorldGraphVisualNodeBase"), "set_generator", "get_generator");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile", PROPERTY_HINT_RESOURCE_TYPE, "TileConfig"), "set_tile", "get_tile");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "preview_color"), "set_preview_color", "get_preview_color");
}
bool IsValid_Recursive(Vector<Ref<WorldGraphVisualNodeBase>>& stack, Ref<WorldGraphVisualNodeBase> node)
{
if (node.is_null()) return false;
for (auto& stackNode : stack)
{
if (stackNode == node)
return false;
}
if (stack.size() > 256)
return false;
stack.push_back(node);
for (auto& input : node->InputNodes)
{
if (input.is_valid() &&
input->IsValid() &&
input->GetInternalNode()->GetInputTypes().size() == input->InputNodes.size())
{
if (!IsValid_Recursive(stack, input))
{
return false;
}
}
else
{
return false;
}
}
if (stack[stack.size() - 1] != node) return false;
stack.remove_at(stack.size() - 1);
return true;
}
bool LayerConfig::IsValid() const
{
Vector<Ref<WorldGraphVisualNodeBase>> nodeStack{};
for (auto tile : Tiles)
{
if (tile.is_null() || tile->Tile.is_null() || tile->TileGenerator.is_null() || !tile->TileGenerator->IsValid() || !IsValid_Recursive(nodeStack, tile->TileGenerator)) return false;
}
return true;
}
bool LayerConfig::CanConnect(Ref<WorldGraphVisualNodeBase> from, Ref<WorldGraphVisualNodeBase> to) const
{
if (from == to) return false;
for (auto input : to->InputNodes)
{
if (input.is_valid() && !CanConnect(from, input)) return false;
}
return true;
}
Ref<ImageTexture> LayerConfig::CreateTexture(Vector2i chunk, int seed) const
{
if (!IsValid()) return {};
Vector<Ref<WorldGraphVisualNodeBase>> inputs{};
Vector<Color> PreviewColors{};
for (int i{}; i < Tiles.size(); ++i)
{
auto& input = Tiles[i];
inputs.push_back(input->TileGenerator);
input->Tile->TileData.SetID(i);
PreviewColors.push_back(input->PreviewColor);
}
Ref<FactoryWorldSettings> settings{};
auto duplicated = duplicate();
auto layerCopy = Ref(Object::cast_to<LayerConfig>(duplicated.ptr()));
layerCopy->StartChunk = -5;
settings->LayerConfigs = {};
settings->LayerConfigs.push_back(layerCopy);
settings->Initialize();
Ref<ImageTexture> texture{};
texture.instantiate();
ChunkGenerator::GenerateChunk(settings, ChunkKey{0, 0}, seed, [PreviewColors, texture] (std::unique_ptr<Chunk>&& chunk)
{
Ref<Image> image;
image.instantiate(Chunk::ChunkSize, Chunk::ChunkSize, false, Image::FORMAT_RGB8);
for (int y{}; y < Chunk::ChunkSize; ++y)
{
for (int x{}; x < Chunk::ChunkSize; ++x)
{
auto tileID = chunk->Tiles[y * Chunk::ChunkSize + x].GetID();
if (tileID < PreviewColors.size()) image->set_pixel(x, Chunk::ChunkSize - y - 1, PreviewColors[tileID]);
else image->set_pixel(x, Chunk::ChunkSize - y - 1, Color{1, 0, 1, 1});
}
}
texture->set_image(image);
});
return texture;
}

View File

@@ -1,21 +0,0 @@
# Factory Core library
add_library(factory_core STATIC
factory_core.cpp
)
# Add alias for consistent usage
add_library(factory::core ALIAS factory_core)
target_include_directories(factory_core
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(factory_core
PUBLIC
flecs::flecs_static
)
# Set compile features
target_compile_features(factory_core PUBLIC cxx_std_17)

View File

@@ -1,5 +0,0 @@
#include "factory_core.hpp"
// Implementation file for factory_core
// Currently empty as Core is header-only, but needed for static library target
// Future implementations will go here

View File

@@ -1,25 +0,0 @@
#pragma once
#include <flecs.h>
namespace factory {
// Core engine class wrapping flecs world
class Core {
public:
Core() : world_() {}
// Access the underlying flecs world
flecs::world& world() { return world_; }
const flecs::world& world() const { return world_; }
// Progress the simulation by one tick
bool progress(float delta_time = 0.0f) {
return world_.progress(delta_time);
}
private:
flecs::world world_;
};
} // namespace factory

24
src/main.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include <iostream>
#include "Components/Configs/WorldConfig.hpp"
#include "Core/WorldInstance.h"
int main()
{
WorldConfig config{};
config.RegisterItem("Stone");
config.RegisterItem("Wood");
config.RegisterItem("Stick");
config.RegisterItem("Copper Ore");
WorldInstance worldInstance{ config };
worldInstance.ProcessFrame();
std::cout << "test\n";
}

View File

@@ -1,15 +0,0 @@
# Test executable
add_executable(factory_core_tests
test_main.cpp
test_example.cpp
)
target_link_libraries(factory_core_tests
PRIVATE
factory_core
doctest::doctest
)
# Register with CTest
include(${doctest_SOURCE_DIR}/scripts/cmake/doctest.cmake)
doctest_discover_tests(factory_core_tests)

View File

@@ -0,0 +1,162 @@
#include <doctest/doctest.h>
#include "Components/Configs/WorldConfig.hpp"
#include "Core/WorldInstance.h"
#include "Components/Chute.hpp"
TEST_SUITE("Chute") {
TEST_CASE("chute transports item from source to destination") {
WorldConfig config{};
uint16_t stoneID = config.RegisterItem("Stone");
WorldInstance world{ config };
// source inventory with items, destination empty
auto source = world.GetEcsWorld().entity();
Inventory_Helper(source, config, 100);
source.ensure<Inventory>().AddItems(stoneID, 3);
auto dest = world.GetEcsWorld().entity();
Inventory_Helper(dest, config, 100);
// vertical drop: (0,10) -> (0,0), should be fast
std::vector<Vector2> path = { {0, 10}, {0, 0} };
auto chuteEntity = world.GetEcsWorld().entity();
Chute_Helper(chuteEntity, path,
source.get<Inventory>(),
dest.get<Inventory>());
// get the transit time
auto chute = chuteEntity.get<Chute>();
uint16_t transitTicks = chute.Data.GetMetaData()->TicksToReachEnd;
CHECK(transitTicks > 0);
// tick once to pull items from source into chute
world.ProcessFrame();
auto srcInv = source.get<Inventory>();
CHECK(srcInv.GetItemsAmount(stoneID) == 0);
// tick until items arrive
for (uint16_t i = 1; i < transitTicks; ++i)
world.ProcessFrame();
auto destInv = dest.get<Inventory>();
CHECK(destInv.GetItemsAmount(stoneID) == 0);
world.ProcessFrame();
destInv = dest.get<Inventory>();
CHECK(destInv.GetItemsAmount(stoneID) == 3);
}
TEST_CASE("chute respects transit time for longer paths") {
WorldConfig config{};
uint16_t ironID = config.RegisterItem("Iron");
WorldInstance world{ config };
auto source = world.GetEcsWorld().entity();
Inventory_Helper(source, config, 100);
source.ensure<Inventory>().AddItems(ironID, 1);
auto dest = world.GetEcsWorld().entity();
Inventory_Helper(dest, config, 100);
// multi-link path with gradual descent
std::vector<Vector2> path = { {0, 10}, {1, 9}, {2, 8}, {3, 7}, {4, 6}, {5, 5} };
auto chuteEntity = world.GetEcsWorld().entity();
Chute_Helper(chuteEntity, path,
source.get<Inventory>(),
dest.get<Inventory>());
auto chute = chuteEntity.get<Chute>();
uint16_t transitTicks = chute.Data.GetMetaData()->TicksToReachEnd;
CHECK(transitTicks > 1);
// pull items into chute
world.ProcessFrame();
// tick one less than transit time — item should not have arrived
for (uint16_t i = 1; i < transitTicks; ++i)
world.ProcessFrame();
auto destInv = dest.get<Inventory>();
CHECK(destInv.GetItemsAmount(ironID) == 0);
// one more tick — item arrives
world.ProcessFrame();
destInv = dest.get<Inventory>();
CHECK(destInv.GetItemsAmount(ironID) == 1);
}
TEST_CASE("chute overflows to world inventory when destination is full") {
WorldConfig config{};
uint16_t stoneID = config.RegisterItem("Stone");
WorldInstance world{ config };
auto source = world.GetEcsWorld().entity();
Inventory_Helper(source, config, 100);
source.ensure<Inventory>().AddItems(stoneID, 3);
// destination can only hold 1
auto dest = world.GetEcsWorld().entity();
Inventory_Helper(dest, config, 1);
std::vector<Vector2> path = { {0, 10}, {0, 0} };
auto chuteEntity = world.GetEcsWorld().entity();
Chute_Helper(chuteEntity, path,
source.get<Inventory>(),
dest.get<Inventory>());
auto chute = chuteEntity.get<Chute>();
uint16_t transitTicks = chute.Data.GetMetaData()->TicksToReachEnd;
// tick enough for all items to arrive
for (uint16_t i = 0; i <= transitTicks; ++i)
world.ProcessFrame();
auto destInv = dest.get<Inventory>();
CHECK(destInv.GetItemsAmount(stoneID) == 1);
auto& worldInv = world.GetEcsWorld().ensure<WorldInventory>();
CHECK(worldInv.GetItemsAmount(stoneID) == 2);
}
TEST_CASE("horizontal chute uses minimum speed") {
WorldConfig config{};
uint16_t woodID = config.RegisterItem("Wood");
WorldInstance world{ config };
auto source = world.GetEcsWorld().entity();
Inventory_Helper(source, config, 100);
source.ensure<Inventory>().AddItems(woodID, 1);
auto dest = world.GetEcsWorld().entity();
Inventory_Helper(dest, config, 100);
// flat path: dy=0 throughout, should use MinSpeed
ChuteConfig chuteConfig{ .Gravity = 1.0f, .MinSpeed = 0.5f };
std::vector<Vector2> path = { {0, 0}, {5, 0} };
auto chuteEntity = world.GetEcsWorld().entity();
Chute_Helper(chuteEntity, path,
source.get<Inventory>(),
dest.get<Inventory>(),
chuteConfig);
auto chute = chuteEntity.get<Chute>();
uint16_t transitTicks = chute.Data.GetMetaData()->TicksToReachEnd;
// distance=5, speed=0.5 -> 10 ticks
CHECK(transitTicks == 10);
// tick enough for item to arrive
for (uint16_t i = 0; i <= transitTicks; ++i)
world.ProcessFrame();
auto destInv = dest.get<Inventory>();
CHECK(destInv.GetItemsAmount(woodID) == 1);
}
}

View File

@@ -0,0 +1,246 @@
#include <doctest/doctest.h>
#include "Components/Configs/WorldConfig.hpp"
#include "Core/WorldInstance.h"
#include "Components/Resource.hpp"
#include "Components/Inventory.hpp"
TEST_SUITE("Resource") {
TEST_CASE("basic resource gathering produces item after one tick") {
WorldConfig config{};
uint16_t stoneID = config.RegisterItem("Stone");
WorldInstance world{ config };
// Create a resource entity with gatherTicks = 1
auto entity = world.GetEcsWorld().entity();
Resource_Ore_Helper(entity, stoneID, 1);
world.ProcessFrame();
auto& inv = world.GetEcsWorld().ensure<WorldInventory>();
CHECK(inv.GetItemsAmount(stoneID) >= 1);
}
TEST_CASE("resource with 20 tick gather time produces item after 20 ticks") {
WorldConfig config{};
uint16_t stoneID = config.RegisterItem("Stone");
WorldInstance world{ config };
auto entity = world.GetEcsWorld().entity();
Resource_Ore_Helper(entity, stoneID, 20);
for (int i = 0; i < 19; ++i)
world.ProcessFrame();
auto& inv = world.GetEcsWorld().ensure<WorldInventory>();
CHECK(inv.GetItemsAmount(stoneID) == 0);
world.ProcessFrame();
CHECK(inv.GetItemsAmount(stoneID) >= 1);
}
}
TEST_SUITE("Resource - Health & Renewing") {
TEST_CASE("tree resource loses health each gather cycle") {
WorldConfig config{};
uint16_t woodID = config.RegisterItem("Wood");
WorldInstance world{ config };
auto entity = world.GetEcsWorld().entity();
Resource_Tree_Helper(entity, woodID, 1, 3, 5);
world.ProcessFrame();
auto& inv = world.GetEcsWorld().ensure<WorldInventory>();
CHECK(inv.GetItemsAmount(woodID) == 1);
auto health = entity.get<ResourceHealth>();
CHECK(health.Health == 2);
}
TEST_CASE("tree enters renewing state when health reaches 0") {
WorldConfig config{};
uint16_t woodID = config.RegisterItem("Wood");
WorldInstance world{ config };
auto entity = world.GetEcsWorld().entity();
Resource_Tree_Helper(entity, woodID, 1, 2, 5);
// 2 gather cycles to deplete health
world.ProcessFrame();
world.ProcessFrame();
auto& inv = world.GetEcsWorld().ensure<WorldInventory>();
CHECK(inv.GetItemsAmount(woodID) == 2);
CHECK(entity.has<Renewing>());
// one more tick should not produce items while renewing
world.ProcessFrame();
CHECK(inv.GetItemsAmount(woodID) == 2);
}
TEST_CASE("tree restores health after renewal period") {
WorldConfig config{};
uint16_t woodID = config.RegisterItem("Wood");
WorldInstance world{ config };
auto entity = world.GetEcsWorld().entity();
Resource_Tree_Helper(entity, woodID, 1, 1, 5);
// 1 gather cycle depletes health
world.ProcessFrame();
CHECK(entity.has<Renewing>());
// 5 ticks for renewal
for (int i = 0; i < 5; ++i)
world.ProcessFrame();
CHECK_FALSE(entity.has<Renewing>());
CHECK(entity.has<FullyGrown>());
auto health = entity.get<ResourceHealth>();
CHECK(health.Health == health.MaxHealth);
}
TEST_CASE("tree resumes gathering after renewal") {
WorldConfig config{};
uint16_t woodID = config.RegisterItem("Wood");
WorldInstance world{ config };
auto entity = world.GetEcsWorld().entity();
Resource_Tree_Helper(entity, woodID, 1, 1, 5);
// deplete
world.ProcessFrame();
auto& inv = world.GetEcsWorld().ensure<WorldInventory>();
CHECK(inv.GetItemsAmount(woodID) == 1);
// renew (5 ticks)
for (int i = 0; i < 5; ++i)
world.ProcessFrame();
CHECK_FALSE(entity.has<Renewing>());
// should gather again
world.ProcessFrame();
CHECK(inv.GetItemsAmount(woodID) == 2);
}
}
TEST_SUITE("Inventory Entity") {
TEST_CASE("resource harvests into local inventory when present") {
WorldConfig config{};
uint16_t stoneID = config.RegisterItem("Stone");
WorldInstance world{ config };
auto entity = world.GetEcsWorld().entity();
Resource_Ore_Helper(entity, stoneID, 1);
Inventory_Helper(entity, config, 10);
world.ProcessFrame();
auto localInv = entity.get<Inventory>();
CHECK(localInv.GetItemsAmount(stoneID) == 1);
auto& worldInv = world.GetEcsWorld().ensure<WorldInventory>();
CHECK(worldInv.GetItemsAmount(stoneID) == 0);
}
TEST_CASE("overflow goes to world inventory when local is full") {
WorldConfig config{};
uint16_t stoneID = config.RegisterItem("Stone");
WorldInstance world{ config };
auto entity = world.GetEcsWorld().entity();
Resource_Ore_Helper(entity, stoneID, 1);
Inventory_Helper(entity, config, 2);
// fill local inventory (max 2)
world.ProcessFrame();
world.ProcessFrame();
auto localInv = entity.get<Inventory>();
CHECK(localInv.GetItemsAmount(stoneID) == 2);
auto& worldInv = world.GetEcsWorld().ensure<WorldInventory>();
CHECK(worldInv.GetItemsAmount(stoneID) == 0);
// next item should overflow to world inventory
world.ProcessFrame();
localInv = entity.get<Inventory>();
CHECK(localInv.GetItemsAmount(stoneID) == 2);
CHECK(worldInv.GetItemsAmount(stoneID) == 1);
}
TEST_CASE("inventory tracks multiple item types independently") {
WorldConfig config{};
uint16_t stoneID = config.RegisterItem("Stone");
uint16_t ironID = config.RegisterItem("Iron");
WorldInstance world{ config };
auto stoneEntity = world.GetEcsWorld().entity();
Resource_Ore_Helper(stoneEntity, stoneID, 1);
Inventory_Helper(stoneEntity, config, 5);
auto ironEntity = world.GetEcsWorld().entity();
Resource_Ore_Helper(ironEntity, ironID, 1);
Inventory_Helper(ironEntity, config, 5);
world.ProcessFrame();
auto stoneInv = stoneEntity.get<Inventory>();
CHECK(stoneInv.GetItemsAmount(stoneID) == 1);
CHECK(stoneInv.GetItemsAmount(ironID) == 0);
auto ironInv = ironEntity.get<Inventory>();
CHECK(ironInv.GetItemsAmount(ironID) == 1);
CHECK(ironInv.GetItemsAmount(stoneID) == 0);
}
TEST_CASE("two producers share a separate inventory entity") {
WorldConfig config{};
uint16_t stoneID = config.RegisterItem("Stone");
uint16_t ironID = config.RegisterItem("Iron");
WorldInstance world{ config };
// inventory entity (chest/barrel)
auto chest = world.GetEcsWorld().entity();
Inventory_Helper(chest, config, 10);
// copy the shared inventory to both producers
auto chestInv = chest.get<Inventory>();
auto stoneProducer = world.GetEcsWorld().entity();
Resource_Ore_Helper(stoneProducer, stoneID, 1);
stoneProducer.set<Inventory>(chestInv);
auto ironProducer = world.GetEcsWorld().entity();
Resource_Ore_Helper(ironProducer, ironID, 1);
ironProducer.set<Inventory>(chestInv);
world.ProcessFrame();
world.ProcessFrame();
world.ProcessFrame();
// all items should be in the shared inventory
auto resultInv = chest.get<Inventory>();
CHECK(resultInv.GetItemsAmount(stoneID) == 3);
CHECK(resultInv.GetItemsAmount(ironID) == 3);
// world inventory should be empty
auto& worldInv = world.GetEcsWorld().ensure<WorldInventory>();
CHECK(worldInv.GetItemsAmount(stoneID) == 0);
CHECK(worldInv.GetItemsAmount(ironID) == 0);
}
}

View File

@@ -0,0 +1,122 @@
#include <doctest/doctest.h>
#include "Components/Configs/WorldConfig.hpp"
#include "Core/WorldInstance.h"
#include "Components/Support.h"
TEST_SUITE("Support") {
TEST_CASE("grounded support has SupportsAvailable equal to MaxSupport") {
WorldInstance world{ WorldConfig{} };
auto e = world.GetEcsWorld().entity();
Support_Helper(e, { 0, 0 }, 5, true);
auto s = e.get<Support>();
CHECK(s.SupportsAvailable == 5);
}
TEST_CASE("vertical neighbor costs 1 point") {
WorldInstance world{ WorldConfig{} };
auto ground = world.GetEcsWorld().entity();
Support_Helper(ground, { 0, 0 }, 5, true);
auto above = world.GetEcsWorld().entity();
Support_Helper(above, { 0, 1 }, 5);
auto s = above.get<Support>();
CHECK(s.SupportsAvailable == 4);
}
TEST_CASE("horizontal neighbor costs 2 points") {
WorldInstance world{ WorldConfig{} };
auto ground = world.GetEcsWorld().entity();
Support_Helper(ground, { 0, 0 }, 5, true);
auto beside = world.GetEcsWorld().entity();
Support_Helper(beside, { 1, 0 }, 5);
auto s = beside.get<Support>();
CHECK(s.SupportsAvailable == 3);
}
TEST_CASE("floating support has SupportsAvailable zero") {
WorldInstance world{ WorldConfig{} };
auto e = world.GetEcsWorld().entity();
Support_Helper(e, { 5, 5 }, 5);
auto s = e.get<Support>();
CHECK(s.SupportsAvailable == 0);
}
TEST_CASE("CanRemove returns false when removal disconnects a support") {
WorldInstance world{ WorldConfig{} };
// chain: ground(0,0) -> (0,1) -> (0,2)
auto ground = world.GetEcsWorld().entity();
Support_Helper(ground, { 0, 0 }, 5, true);
auto mid = world.GetEcsWorld().entity();
Support_Helper(mid, { 0, 1 }, 5);
auto top = world.GetEcsWorld().entity();
Support_Helper(top, { 0, 2 }, 5);
// removing (0,1) disconnects (0,2)
std::vector<Vector2> toRemove = { { 0, 1 } };
CHECK_FALSE(CanRemove(world.GetEcsWorld(), toRemove));
}
TEST_CASE("CanRemove returns true when removing a leaf node") {
WorldInstance world{ WorldConfig{} };
auto ground = world.GetEcsWorld().entity();
Support_Helper(ground, { 0, 0 }, 5, true);
auto leaf = world.GetEcsWorld().entity();
Support_Helper(leaf, { 0, 1 }, 5);
// removing the leaf leaves only the grounded anchor — valid
std::vector<Vector2> toRemove = { { 0, 1 } };
CHECK(CanRemove(world.GetEcsWorld(), toRemove));
}
TEST_CASE("CanRemove returns false when removal would unsupport a RequiresSupport entity") {
WorldInstance world{ WorldConfig{} };
// grounded support at (0,0); machine one tile above at (0,1)
auto ground = world.GetEcsWorld().entity();
Support_Helper(ground, { 0, 0 }, 5, true);
auto machine = world.GetEcsWorld().entity();
machine.set<TilePosition>({ { 0, 1 } });
machine.add<RequiresSupport>();
// removing the only support at (0,0) leaves the machine at (0,1) unsupported
std::vector<Vector2> toRemove = { { 0, 0 } };
CHECK_FALSE(CanRemove(world.GetEcsWorld(), toRemove));
}
TEST_CASE("CanRemove returns true when RequiresSupport entity still has support") {
WorldInstance world{ WorldConfig{} };
// chain: ground(0,0) -> leaf(0,1); machine one tile above leaf at (0,2)
auto ground = world.GetEcsWorld().entity();
Support_Helper(ground, { 0, 0 }, 5, true);
auto leaf = world.GetEcsWorld().entity();
Support_Helper(leaf, { 0, 1 }, 5);
auto machine = world.GetEcsWorld().entity();
machine.set<TilePosition>({ { 0, 2 } });
machine.add<RequiresSupport>();
// sibling at (1,0) is a horizontal leaf; removing it doesn't affect (0,1)
auto sibling = world.GetEcsWorld().entity();
Support_Helper(sibling, { 1, 0 }, 5);
std::vector<Vector2> toRemove = { { 1, 0 } };
CHECK(CanRemove(world.GetEcsWorld(), toRemove));
}
}

167
tests/Util/test_Grid8x8.cpp Normal file
View File

@@ -0,0 +1,167 @@
#include <doctest/doctest.h>
#include <vector>
#include "Types/Grid8x8.h"
TEST_SUITE("Grid8x8")
{
TEST_CASE("default construction - all cells empty")
{
Grid8x8 g;
CHECK(g.Bits == 0);
CHECK(g.Count() == 0);
}
TEST_CASE("Set and Get single cell")
{
Grid8x8 g;
g.Set(3, 5);
CHECK(g.Get(3, 5));
CHECK_FALSE(g.Get(0, 0));
CHECK_FALSE(g.Get(3, 4));
CHECK_FALSE(g.Get(4, 5));
}
TEST_CASE("Clear cell")
{
Grid8x8 g;
g.Set(2, 2);
CHECK(g.Get(2, 2));
g.Clear(2, 2);
CHECK_FALSE(g.Get(2, 2));
CHECK(g.Count() == 0);
}
TEST_CASE("Set does not affect other cells")
{
Grid8x8 g;
g.Set(0, 0);
g.Set(7, 7);
CHECK(g.Get(0, 0));
CHECK(g.Get(7, 7));
CHECK_FALSE(g.Get(0, 7));
CHECK_FALSE(g.Get(7, 0));
CHECK(g.Count() == 2);
}
TEST_CASE("Count matches number of set cells")
{
Grid8x8 g;
g.Set(0, 0);
g.Set(1, 0);
g.Set(0, 1);
CHECK(g.Count() == 3);
g.Clear(1, 0);
CHECK(g.Count() == 2);
}
TEST_CASE("Set same cell twice does not increase Count")
{
Grid8x8 g;
g.Set(4, 4);
g.Set(4, 4);
CHECK(g.Count() == 1);
}
TEST_CASE("iterator - empty grid yields no cells")
{
Grid8x8 g;
int count = 0;
for (auto cell : g)
++count;
CHECK(count == 0);
}
TEST_CASE("iterator - single cell")
{
Grid8x8 g;
g.Set(3, 6);
std::vector<Grid8x8::Cell> cells;
for (auto cell : g)
cells.push_back(cell);
REQUIRE(cells.size() == 1);
CHECK(cells[0].X == 3);
CHECK(cells[0].Y == 6);
}
TEST_CASE("iterator - corners")
{
Grid8x8 g;
g.Set(0, 0);
g.Set(7, 0);
g.Set(0, 7);
g.Set(7, 7);
std::vector<Grid8x8::Cell> cells;
for (auto cell : g)
cells.push_back(cell);
REQUIRE(cells.size() == 4);
// Iterator visits in bit-index order (row-major: left-to-right, top-to-bottom)
CHECK(cells[0].X == 0); CHECK(cells[0].Y == 0);
CHECK(cells[1].X == 7); CHECK(cells[1].Y == 0);
CHECK(cells[2].X == 0); CHECK(cells[2].Y == 7);
CHECK(cells[3].X == 7); CHECK(cells[3].Y == 7);
}
TEST_CASE("iterator - full grid visits all 64 cells")
{
Grid8x8 g;
g.Bits = ~uint64_t(0);
int count = 0;
bool seen[8][8] = {};
for (auto cell : g)
{
CHECK(cell.X >= 0); CHECK(cell.X < 8);
CHECK(cell.Y >= 0); CHECK(cell.Y < 8);
CHECK_FALSE(seen[cell.Y][cell.X]);
seen[cell.Y][cell.X] = true;
++count;
}
CHECK(count == 64);
}
TEST_CASE("iterator count matches Count()")
{
Grid8x8 g;
g.Set(1, 2);
g.Set(3, 4);
g.Set(5, 6);
g.Set(7, 1);
int iterated = 0;
for (auto cell : g)
++iterated;
CHECK(iterated == g.Count());
}
TEST_CASE("iterator - coordinates decode correctly for every cell")
{
for (int y = 0; y < 8; ++y)
{
for (int x = 0; x < 8; ++x)
{
Grid8x8 g;
g.Set(x, y);
int count = 0;
for (auto cell : g)
{
CHECK(cell.X == x);
CHECK(cell.Y == y);
++count;
}
CHECK(count == 1);
}
}
}
TEST_CASE("size is 8 bytes")
{
static_assert(sizeof(Grid8x8) == 8);
}
}

View File

@@ -0,0 +1,431 @@
#include <doctest/doctest.h>
#include <atomic>
#include "Util/SharedBuffer.h"
#include <string>
#include <vector>
#include <thread>
struct SimpleMetaData
{
int Id{};
float Value{};
};
TEST_SUITE("SharedBuffer")
{
TEST_CASE("default construction")
{
SharedBuffer<int, SimpleMetaData> buf;
CHECK_FALSE(buf);
}
TEST_CASE("sized construction")
{
SimpleMetaData meta{ 42, 3.14f };
SharedBuffer<int, SimpleMetaData> buf(5, meta);
CHECK(buf);
CHECK(buf.GetSize() == 5);
CHECK(buf.GetMetaData()->Id == 42);
CHECK(buf.GetMetaData()->Value == doctest::Approx(3.14f));
for (uint32_t i = 0; i < buf.GetSize(); ++i)
CHECK(buf[i] == 0);
}
TEST_CASE("element access and mutation")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> buf(3, meta);
buf[0] = 10;
buf[1] = 20;
buf[2] = 30;
CHECK(buf[0] == 10);
CHECK(buf[1] == 20);
CHECK(buf[2] == 30);
}
TEST_CASE("Ptr() access")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> buf(3, meta);
buf[0] = 100;
int* ptr = buf.Ptr();
CHECK(ptr[0] == 100);
ptr[1] = 200;
CHECK(buf[1] == 200);
}
TEST_CASE("GetData() returns valid span")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> buf(4, meta);
buf[0] = 1;
buf[1] = 2;
buf[2] = 3;
buf[3] = 4;
auto span = buf.GetData();
CHECK(span.size() == 4);
CHECK(span[0] == 1);
CHECK(span[3] == 4);
}
TEST_CASE("const access")
{
SimpleMetaData meta{ 7, 1.0f };
SharedBuffer<int, SimpleMetaData> buf(2, meta);
buf[0] = 99;
const auto& cbuf = buf;
CHECK(cbuf[0] == 99);
CHECK(cbuf.GetSize() == 2);
CHECK(cbuf.Ptr()[0] == 99);
CHECK(cbuf.GetMetaData()->Id == 7);
CHECK(cbuf.GetData().size() == 2);
}
TEST_CASE("copy constructor shares data")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> a(3, meta);
a[0] = 42;
SharedBuffer<int, SimpleMetaData> b(a);
CHECK(b);
CHECK(b.GetSize() == 3);
CHECK(b[0] == 42);
// They share the same underlying data
CHECK(a.Ptr() == b.Ptr());
// Mutation through one is visible in the other
a[1] = 77;
CHECK(b[1] == 77);
}
TEST_CASE("copy assignment shares data")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> a(2, meta);
a[0] = 10;
SharedBuffer<int, SimpleMetaData> b;
b = a;
CHECK(b);
CHECK(b[0] == 10);
CHECK(a.Ptr() == b.Ptr());
}
TEST_CASE("copy assignment from non-empty to non-empty")
{
SimpleMetaData meta1{};
SimpleMetaData meta2{};
SharedBuffer<int, SimpleMetaData> a(2, meta1);
SharedBuffer<int, SimpleMetaData> b(3, meta2);
a[0] = 1;
b[0] = 2;
int* oldBPtr = b.Ptr();
b = a;
CHECK(b.GetSize() == 2);
CHECK(b[0] == 1);
CHECK(b.Ptr() == a.Ptr());
CHECK(b.Ptr() != oldBPtr);
}
TEST_CASE("self copy assignment")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> a(2, meta);
a[0] = 55;
a = a;
CHECK(a);
CHECK(a[0] == 55);
CHECK(a.GetSize() == 2);
}
TEST_CASE("move constructor transfers ownership")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> a(3, meta);
a[0] = 42;
int* origPtr = a.Ptr();
SharedBuffer<int, SimpleMetaData> b(std::move(a));
CHECK(b);
CHECK(b[0] == 42);
CHECK(b.Ptr() == origPtr);
CHECK_FALSE(a); // source is empty
}
TEST_CASE("move assignment transfers ownership")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> a(3, meta);
a[0] = 42;
int* origPtr = a.Ptr();
SharedBuffer<int, SimpleMetaData> b;
b = std::move(a);
CHECK(b);
CHECK(b[0] == 42);
CHECK(b.Ptr() == origPtr);
CHECK_FALSE(a);
}
TEST_CASE("self move assignment")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> a(2, meta);
a[0] = 55;
a = std::move(a);
CHECK(a);
CHECK(a[0] == 55);
}
TEST_CASE("reference counting - last copy cleans up")
{
SimpleMetaData meta{};
int* ptr;
{
SharedBuffer<int, SimpleMetaData> a(3, meta);
a[0] = 1;
ptr = a.Ptr();
{
SharedBuffer<int, SimpleMetaData> b(a);
CHECK(b.Ptr() == ptr);
// b goes out of scope - should NOT free since a still alive
}
// a should still be valid
CHECK(a);
CHECK(a[0] == 1);
}
// a goes out of scope here - memory freed (no way to check, but no crash)
}
TEST_CASE("multiple copies and sequential destruction")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> a(2, meta);
a[0] = 100;
SharedBuffer<int, SimpleMetaData> b(a);
SharedBuffer<int, SimpleMetaData> c(b);
SharedBuffer<int, SimpleMetaData> d(c);
CHECK(a.Ptr() == d.Ptr());
// Destroy in various orders
b = SharedBuffer<int, SimpleMetaData>(); // release b's ref
CHECK_FALSE(b);
CHECK(a[0] == 100); // a still valid
d = SharedBuffer<int, SimpleMetaData>(); // release d's ref
CHECK(a[0] == 100); // a still valid
CHECK(c[0] == 100); // c still valid
}
TEST_CASE("reassignment releases old buffer")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> a(2, meta);
SharedBuffer<int, SimpleMetaData> b(3, meta);
a[0] = 1;
b[0] = 2;
// a had sole ownership of its buffer; assigning b should free old buffer
a = b;
CHECK(a[0] == 2);
CHECK(a.GetSize() == 3);
}
TEST_CASE("works with non-trivial element types")
{
struct Meta { int x{}; };
SharedBuffer<std::string, Meta> buf(3, Meta{ 1 });
buf[0] = "hello";
buf[1] = "world";
buf[2] = "test";
CHECK(buf[0] == "hello");
CHECK(buf[1] == "world");
CHECK(buf[2] == "test");
// Copy and verify strings are shared
SharedBuffer<std::string, Meta> copy(buf);
CHECK(copy[0] == "hello");
buf[0] = "modified";
CHECK(copy[0] == "modified"); // shared data
}
TEST_CASE("copy of non-trivial types cleans up properly")
{
struct Meta { int x{}; };
{
SharedBuffer<std::string, Meta> a(2, Meta{});
a[0] = "aaa";
a[1] = "bbb";
{
SharedBuffer<std::string, Meta> b(a);
CHECK(b[0] == "aaa");
}
// b destroyed, a still valid
CHECK(a[0] == "aaa");
}
// a destroyed, strings cleaned up (no leak/crash)
}
TEST_CASE("zero-size buffer")
{
SimpleMetaData meta{ 1, 2.0f };
SharedBuffer<int, SimpleMetaData> buf(0, meta);
CHECK(buf);
CHECK(buf.GetSize() == 0);
CHECK(buf.GetMetaData()->Id == 1);
CHECK(buf.GetData().empty());
}
TEST_CASE("metadata mutation")
{
SimpleMetaData meta{ 1, 0.0f };
SharedBuffer<int, SimpleMetaData> buf(1, meta);
buf.GetMetaData()->Id = 99;
buf.GetMetaData()->Value = 1.5f;
CHECK(buf.GetMetaData()->Id == 99);
CHECK(buf.GetMetaData()->Value == doctest::Approx(1.5f));
}
TEST_CASE("metadata shared between copies")
{
SimpleMetaData meta{ 10, 0.0f };
SharedBuffer<int, SimpleMetaData> a(1, meta);
SharedBuffer<int, SimpleMetaData> b(a);
a.GetMetaData()->Id = 50;
CHECK(b.GetMetaData()->Id == 50);
}
TEST_CASE("bool conversion")
{
SharedBuffer<int, SimpleMetaData> empty;
CHECK_FALSE(empty);
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> valid(1, meta);
CHECK(valid);
SharedBuffer<int, SimpleMetaData> moved(std::move(valid));
CHECK_FALSE(valid);
CHECK(moved);
}
TEST_CASE("copy from default-constructed buffer")
{
SharedBuffer<int, SimpleMetaData> empty;
SharedBuffer<int, SimpleMetaData> copy(empty);
CHECK_FALSE(copy);
}
TEST_CASE("assign default-constructed buffer")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> a(2, meta);
SharedBuffer<int, SimpleMetaData> empty;
a = empty;
CHECK_FALSE(a);
}
TEST_CASE("move from default-constructed buffer")
{
SharedBuffer<int, SimpleMetaData> empty;
SharedBuffer<int, SimpleMetaData> moved(std::move(empty));
CHECK_FALSE(moved);
}
TEST_CASE("large buffer")
{
SimpleMetaData meta{};
constexpr int N = 10000;
SharedBuffer<int, SimpleMetaData> buf(N, meta);
for (int i = 0; i < N; ++i)
buf[i] = i * 2;
for (int i = 0; i < N; ++i)
CHECK(buf[i] == i * 2);
}
TEST_CASE("chain of assignments")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> a(1, meta);
SharedBuffer<int, SimpleMetaData> b(1, meta);
SharedBuffer<int, SimpleMetaData> c(1, meta);
a[0] = 1;
b[0] = 2;
c[0] = 3;
a = b;
b = c;
CHECK(a[0] == 2);
CHECK(b[0] == 3);
CHECK(c[0] == 3);
CHECK(b.Ptr() == c.Ptr());
}
TEST_CASE("concurrent read access from copies")
{
SimpleMetaData meta{};
SharedBuffer<int, SimpleMetaData> buf(100, meta);
for (uint32_t i = 0; i < 100; ++i)
buf[i] = static_cast<int>(i);
auto copy1 = buf;
auto copy2 = buf;
std::thread t1([&copy1]() {
int sum = 0;
for (uint32_t i = 0; i < copy1.GetSize(); ++i)
sum += copy1[i];
CHECK(sum == 4950);
});
std::thread t2([&copy2]() {
int sum = 0;
for (uint32_t i = 0; i < copy2.GetSize(); ++i)
sum += copy2[i];
CHECK(sum == 4950);
});
t1.join();
t2.join();
}
}

View File

@@ -1,30 +0,0 @@
#include <doctest/doctest.h>
#include "factory_core.hpp"
TEST_CASE("Core initialization") {
factory::Core core;
SUBCASE("World is valid after construction") {
CHECK(core.world().id() != 0);
}
SUBCASE("Progress returns true") {
CHECK(core.progress() == true);
}
}
TEST_CASE("Basic ECS operations") {
factory::Core core;
auto& world = core.world();
SUBCASE("Can create entity") {
auto entity = world.entity();
CHECK(entity.is_valid());
}
SUBCASE("Can create entity with name") {
auto entity = world.entity("test_entity");
CHECK(entity.is_valid());
CHECK(entity.name() == std::string("test_entity"));
}
}