basic graph and nodes + serialization

This commit is contained in:
Connor
2026-02-22 11:03:41 +09:00
parent cd745f0eda
commit 9b0c9a87fa
12 changed files with 1698 additions and 947 deletions

View File

@@ -1,30 +0,0 @@
#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

@@ -1,70 +0,0 @@
#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

@@ -1,665 +0,0 @@
#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

@@ -1,174 +0,0 @@
#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,109 @@
#pragma once
#include "WorldGraph/WorldGraphNode.h"
#include <memory>
#include <optional>
#include <unordered_map>
#include <unordered_set>
namespace WorldGraph {
class GraphSerializer;
/// Node-based graph for a single procedural world-generation pass.
///
/// Usage overview
/// ──────────────
/// 1. Call AddNode() for each node you need; keep the returned NodeIDs.
/// 2. Wire outputs to inputs with Connect(fromNode, toNode, inputSlot).
/// 3. Call Evaluate(outputNodeID, ctx) per cell to get its tile ID.
///
/// Evaluation is recursive and performed on the fly. Unconnected inputs
/// receive a zero value of their declared type; the caller is responsible
/// for checking IsValid() if all inputs must be wired.
///
/// Cycle detection runs at Connect() time: a connection that would create a
/// cycle is rejected and false is returned.
class Graph {
public:
using NodeID = uint32_t;
static constexpr NodeID INVALID_ID = 0;
// ── Node management ────────────────────────────────────────────────────
/// Add a node; returns its assigned ID (always > INVALID_ID).
NodeID AddNode(std::unique_ptr<Node> node);
/// Remove a node and sever every connection that references it.
void RemoveNode(NodeID id);
/// Look up a node by ID (returns nullptr if not found).
Node* GetNode(NodeID id);
const Node* GetNode(NodeID id) const;
size_t NodeCount() const { return nodes.size(); }
// ── Connection management ─────────────────────────────────────────────
/// Wire the output of \p fromNode into input slot \p inputSlot of \p toNode.
///
/// Returns false (and makes no change) when:
/// - either node is not in the graph, or
/// - the connection would create a directed cycle.
///
/// Connecting to an already-wired slot replaces the previous connection.
bool Connect(NodeID fromNode, NodeID toNode, int inputSlot);
/// Remove the wire going into \p toNode's \p inputSlot (no-op if not wired).
void Disconnect(NodeID toNode, int inputSlot);
/// Return the source node wired to (toNode, inputSlot), or nullopt.
std::optional<NodeID> GetInput(NodeID toNode, int inputSlot) const;
// ── Evaluation ────────────────────────────────────────────────────────
/// Recursively evaluate the subgraph rooted at \p outputNode for the cell
/// described by \p ctx. Call AsInt() on the result to obtain a tile ID.
///
/// Unconnected inputs default to zero. Returns a zero Float value if
/// \p outputNode is not in the graph.
Value Evaluate(NodeID outputNode, const EvalContext& ctx) const;
/// True when every required input of \p outputNode (and all its transitive
/// dependencies) is connected.
bool IsValid(NodeID outputNode) const;
private:
friend class GraphSerializer;
NodeID nextID { 1 };
std::unordered_map<NodeID, std::unique_ptr<Node>> nodes;
// Connection map: (toNode, inputSlot) → fromNode
struct ConnKey {
NodeID toNode;
int slot;
bool operator==(const ConnKey& o) const noexcept {
return toNode == o.toNode && slot == o.slot;
}
};
struct ConnKeyHash {
size_t operator()(const ConnKey& k) const noexcept {
return std::hash<uint64_t>{}(
(static_cast<uint64_t>(k.toNode) << 32) | static_cast<uint32_t>(k.slot));
}
};
std::unordered_map<ConnKey, NodeID, ConnKeyHash> connections;
// Returns true if adding an edge (from → to) would create a cycle.
// This holds when 'to' is already a transitive dependency of 'from'.
bool WouldCreateCycle(NodeID from, NodeID to) const;
// Recursively collect 'id' and all its upstream dependencies into 'out'.
void CollectDependencies(NodeID id, std::unordered_set<NodeID>& out) const;
Value EvaluateImpl(NodeID id, const EvalContext& ctx) const;
};
} // namespace WorldGraph

View File

@@ -0,0 +1,256 @@
#pragma once
#include "WorldGraph/WorldGraphTypes.h"
#include "config.h"
#include <cstdint>
#include <string>
#include <vector>
namespace WorldGraph {
// ─────────────────────────────── Node base ───────────────────────────────────
/// Abstract base for every node in the world-generation graph.
///
/// A node declares its output type and input types, and implements Evaluate()
/// to compute an output Value from its inputs and the per-cell EvalContext.
/// Type declarations are advisory — coercion is available on Value — but they
/// allow the future visual editor to flag mismatched connections.
class Node {
public:
virtual ~Node() = default;
virtual Type GetOutputType() const = 0;
virtual std::vector<Type> GetInputTypes() const = 0;
/// Compute the output value.
/// \p inputs is guaranteed to have exactly GetInputCount() entries.
virtual Value Evaluate(const EvalContext& ctx,
const std::vector<Value>& inputs) const = 0;
virtual std::string GetName() const = 0;
size_t GetInputCount() const { return GetInputTypes().size(); }
};
// ─────────────────────────────── Math nodes ──────────────────────────────────
/// Outputs a + b (Float)
class AddNode : public Node {
public:
Type GetOutputType() const override { return Type::Float; }
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
std::string GetName() const override { return "Add"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
return Value::MakeFloat(in[0].AsFloat() + in[1].AsFloat());
};
};
/// Outputs a b (Float)
class SubtractNode : public Node {
public:
Type GetOutputType() const override { return Type::Float; }
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
std::string GetName() const override { return "Subtract"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
return Value::MakeFloat(in[0].AsFloat() - in[1].AsFloat());
}
};
/// Outputs a × b (Float)
class MultiplyNode : public Node {
public:
Type GetOutputType() const override { return Type::Float; }
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
std::string GetName() const override { return "Multiply"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
return Value::MakeFloat(in[0].AsFloat() * in[1].AsFloat());
}
};
/// Outputs a / b (Float; returns 0 on division by zero)
class DivideNode : public Node {
public:
Type GetOutputType() const override { return Type::Float; }
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
std::string GetName() const override { return "Divide"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
float b = in[1].AsFloat();
if (b == 0.0f) return Value::MakeFloat(0.0f);
return Value::MakeFloat(in[0].AsFloat() / b);
}
};
// ─────────────────────────────── Comparison nodes ────────────────────────────
/// Outputs true when a < b
class LessNode : public Node {
public:
Type GetOutputType() const override { return Type::Bool; }
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
std::string GetName() const override { return "Less"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
return Value::MakeBool(in[0].AsFloat() < in[1].AsFloat());
}
};
/// Outputs true when a > b
class GreaterNode : public Node {
public:
Type GetOutputType() const override { return Type::Bool; }
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
std::string GetName() const override { return "Greater"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
return Value::MakeBool(in[0].AsFloat() > in[1].AsFloat());
}
};
/// Outputs true when a <= b
class LessEqualNode : public Node {
public:
Type GetOutputType() const override { return Type::Bool; }
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
std::string GetName() const override { return "LessEqual"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
return Value::MakeBool(in[0].AsFloat() <= in[1].AsFloat());
}
};
/// Outputs true when a >= b
class GreaterEqualNode : public Node {
public:
Type GetOutputType() const override { return Type::Bool; }
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
std::string GetName() const override { return "GreaterEqual"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
return Value::MakeBool(in[0].AsFloat() >= in[1].AsFloat());
}
};
/// Outputs true when a == b (float comparison; use with care for non-integers)
class EqualNode : public Node {
public:
Type GetOutputType() const override { return Type::Bool; }
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
std::string GetName() const override { return "Equal"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
return Value::MakeBool(in[0].AsFloat() == in[1].AsFloat());
}
};
// ─────────────────────────────── Boolean logic ───────────────────────────────
/// Outputs a && b
class AndNode : public Node {
public:
Type GetOutputType() const override { return Type::Bool; }
std::vector<Type> GetInputTypes() const override { return { Type::Bool, Type::Bool }; }
std::string GetName() const override { return "And"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
return Value::MakeBool(in[0].AsBool() && in[1].AsBool());
}
};
/// Outputs a || b
class OrNode : public Node {
public:
Type GetOutputType() const override { return Type::Bool; }
std::vector<Type> GetInputTypes() const override { return { Type::Bool, Type::Bool }; }
std::string GetName() const override { return "Or"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 2);
return Value::MakeBool(in[0].AsBool() || in[1].AsBool());
}
};
/// Outputs !a
class NotNode : public Node {
public:
Type GetOutputType() const override { return Type::Bool; }
std::vector<Type> GetInputTypes() const override { return { Type::Bool }; }
std::string GetName() const override { return "Not"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 1);
return Value::MakeBool(!in[0].AsBool());
}
};
// ─────────────────────────────── Control flow ─────────────────────────────────
/// Selects between two inputs based on a boolean condition.
///
/// inputs[0] condition (Bool)
/// inputs[1] value when true
/// inputs[2] value when false
///
/// The output Value preserves the concrete type of whichever branch is chosen,
/// so Int tile IDs pass through correctly even though GetOutputType() reports Float.
class BranchNode : public Node {
public:
Type GetOutputType() const override { return Type::Float; }
std::vector<Type> GetInputTypes() const override {
return { Type::Bool, Type::Float, Type::Float };
}
std::string GetName() const override { return "Branch"; }
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
DEV_ASSERT(in.size() == 3);
return in[0].AsBool() ? in[1] : in[2];
}
};
// ─────────────────────────────── Source / constant nodes ─────────────────────
/// Outputs a fixed floating-point constant (no inputs).
class ConstantNode : public Node {
public:
float value { 0.0f };
explicit ConstantNode(float v = 0.0f) : value(v) {}
Type GetOutputType() const override { return Type::Float; }
std::vector<Type> GetInputTypes() const override { return {}; }
std::string GetName() const override { return "Constant"; }
Value Evaluate(const EvalContext&, const std::vector<Value>&) const override { return Value::MakeFloat(value); };
};
/// Outputs a tile identifier as an integer (no inputs).
class IDNode : public Node {
public:
int32_t tileID { 0 };
explicit IDNode(int32_t id = 0) : tileID(id) {}
Type GetOutputType() const override { return Type::Int; }
std::vector<Type> GetInputTypes() const override { return {}; }
std::string GetName() const override { return "TileID"; }
Value Evaluate(const EvalContext&, const std::vector<Value>&) const override { return Value::MakeInt(tileID); };
};
/// Outputs the world-space X coordinate of the cell being evaluated.
class PositionXNode : public Node {
public:
Type GetOutputType() const override { return Type::Int; }
std::vector<Type> GetInputTypes() const override { return {}; }
std::string GetName() const override { return "PositionX"; }
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override { return Value::MakeInt(ctx.worldX); };
};
/// Outputs the world-space Y coordinate of the cell being evaluated.
class PositionYNode : public Node {
public:
Type GetOutputType() const override { return Type::Int; }
std::vector<Type> GetInputTypes() const override { return {}; }
std::string GetName() const override { return "PositionY"; }
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override { return Value::MakeInt(ctx.worldY); };
};
} // namespace WorldGraph

View File

@@ -0,0 +1,56 @@
#pragma once
#include "WorldGraph/WorldGraph.h"
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
namespace WorldGraph {
/// Serializes and deserializes a Graph to/from JSON using nlohmann/json.
///
/// JSON format
/// ───────────
/// {
/// "nextId": <uint>,
/// "nodes": [
/// { "id": <uint>, "type": "<GetName()>", ...node-specific fields... },
/// ...
/// ],
/// "connections": [
/// { "from": <uint>, "to": <uint>, "slot": <int> },
/// ...
/// ]
/// }
///
/// Node-specific fields
/// ────────────────────
/// Constant : "value" (float)
/// TileID : "tileId" (int)
/// All other nodes have no extra fields.
class GraphSerializer {
public:
/// Serialise \p graph to a JSON object.
static nlohmann::json ToJson (const Graph& graph);
/// Reconstruct a Graph from a JSON object produced by ToJson().
/// Returns nullopt if the JSON is structurally invalid or contains an
/// unrecognised node type.
static std::optional<Graph> FromJson(const nlohmann::json& j);
/// Write the graph as pretty-printed JSON to \p path.
/// Returns false if the file could not be opened/written.
static bool Save(const Graph& graph, const std::string& path);
/// Read and reconstruct a Graph from \p path.
/// Returns nullopt if the file cannot be read or the JSON is invalid.
static std::optional<Graph> Load(const std::string& path);
private:
/// Construct a Node of the given type name, reading any config fields from \p j.
/// Returns nullptr for unrecognised type names.
static std::unique_ptr<Node> CreateNode(const std::string& type,
const nlohmann::json& j);
};
} // namespace WorldGraph

View File

@@ -0,0 +1,77 @@
#pragma once
#include <cstdint>
namespace WorldGraph {
/// Types that can flow through graph edges.
enum class Type : uint8_t {
Int, ///< Integer — used for tile IDs, positions, counts.
Float, ///< Floating-point — used for math and noise.
Bool, ///< Boolean — used for conditions / comparisons.
};
/// A tagged value flowing along a graph edge.
/// Provides coercion helpers so nodes can request any type they need.
struct Value {
Type type { Type::Float };
union { int32_t i; float f; bool b; } data {};
inline static Value MakeInt (int32_t v) noexcept { Value r; r.type = Type::Int; r.data.i = v; return r; }
inline static Value MakeFloat(float v) noexcept { Value r; r.type = Type::Float; r.data.f = v; return r; }
inline static Value MakeBool (bool v) noexcept { Value r; r.type = Type::Bool; r.data.b = v; return r; }
float AsFloat() const noexcept;
int32_t AsInt () const noexcept;
bool AsBool () const noexcept;
bool operator==(const Value& o) const noexcept;
bool operator!=(const Value& o) const noexcept { return !(*this == o); }
};
/// Per-cell context forwarded to every node during graph evaluation.
struct EvalContext {
int32_t worldX { 0 }; ///< World-space tile column.
int32_t worldY { 0 }; ///< World-space tile row.
uint64_t seed { 0 }; ///< World seed for deterministic noise.
// TODO: previous-pass tile accessor for tile-query nodes.
};
inline float Value::AsFloat() const noexcept {
switch (type) {
case Type::Float: return data.f;
case Type::Int: return static_cast<float>(data.i);
case Type::Bool: return data.b ? 1.0f : 0.0f;
}
return 0.0f;
}
inline int32_t Value::AsInt() const noexcept {
switch (type) {
case Type::Int: return data.i;
case Type::Float: return static_cast<int32_t>(data.f);
case Type::Bool: return data.b ? 1 : 0;
}
return 0;
}
inline bool Value::AsBool() const noexcept {
switch (type) {
case Type::Bool: return data.b;
case Type::Int: return data.i != 0;
case Type::Float: return data.f != 0.0f;
}
return false;
}
inline bool Value::operator==(const Value& o) const noexcept {
if (type != o.type) return false;
switch (type) {
case Type::Int: return data.i == o.data.i;
case Type::Float: return data.f == o.data.f;
case Type::Bool: return data.b == o.data.b;
}
return false;
}
} // namespace WorldGraph