Completely rework the algorithm

This commit is contained in:
shylie 2026-05-04 11:29:38 -04:00
parent f6c0e07489
commit 5d63e04b17
Signed by: shylie
GPG Key ID: 20C8D70580687D9D
17 changed files with 683 additions and 741 deletions

7
.clangd Normal file
View File

@ -0,0 +1,7 @@
Documentation:
CommentFormat: Doxygen
If:
PathMatch: .*\.hip
CompileFlags:
Add: [-x, hip]

View File

@ -1,33 +1,47 @@
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.21)
project(sand)
project(sand LANGUAGES CXX HIP)
find_package(OpenMP REQUIRED)
find_package(OpenMP)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LUAJIT REQUIRED luajit)
add_library(sand SHARED
add_library(sand STATIC
include/sand/sand.h
include/sand/type.h
include/sand/type_builder.h
include/sand/type_range.h
include/sand/rule.h
include/sand/rule_builder.h
src/type.cpp
src/type_builder.cpp
src/type_range.cpp
src/rule.cpp
src/rule_builder.cpp
src/sand.cpp
src/lualib.cpp
src/sand.hip
)
target_include_directories(sand
PUBLIC include
PRIVATE ${LUAJIT_INCLUDE_DIRS}
)
target_link_libraries(sand PRIVATE ${LUAJIT_LIBRARIES} OpenMP::OpenMP_CXX)
target_include_directories(sand PUBLIC include)
set_target_properties(sand PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE ON
)
if (EXISTS third/tracy/CMakeLists.txt)
option(TRACY_ENABLE "Enable Tracy" ON)
set(TRACY_STATIC OFF)
set(TRACY_ON_DEMAND ON)
if(OpenMP_FOUND)
target_link_libraries(sand PRIVATE OpenMP::OpenMP_CXX)
endif()
if(EXISTS third/tracy/CMakeLists.txt)
add_subdirectory(third/tracy)
target_link_libraries(sand PUBLIC Tracy::TracyClient)
if(NOT TRACY_ENABLE)
target_compile_definitions(sand PUBLIC FrameMark ZoneScoped)
endif()
else()
target_compile_definitions(sand PUBLIC FrameMark ZoneScoped)
endif()

View File

@ -1,121 +0,0 @@
#ifndef SAND_H
#define SAND_H
#include <cstdint>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
namespace sand
{
struct type
{
uint16_t id;
uint16_t default_result;
};
using type_map = std::unordered_map<std::string, type>;
class RulesBuilder;
class TypeBuilder
{
friend RulesBuilder;
public:
TypeBuilder();
TypeBuilder& add_type(const std::string& name);
TypeBuilder& add_type(const std::string& name,
const std::string& default_result);
RulesBuilder finish();
private:
uint16_t next_type_id = 0;
type_map types;
}; // class TypeBuilder
class Rule;
class Sand;
class RulesBuilder
{
friend TypeBuilder;
friend Rule;
friend Sand;
public:
Rule add_rule(const std::string& source_name, const std::string& to);
Sand build(uint16_t width, uint16_t height, const std::string& initial);
private:
RulesBuilder(const TypeBuilder& type_builder);
void finish_rule(const Rule& rule);
type_map types;
std::unordered_map<uint16_t,
std::vector<std::pair<unsigned __int128, uint16_t>>>
rules;
}; // class RulesBuilder
class Rule
{
friend RulesBuilder;
public:
using match_fn = std::function<bool(const std::string&)>;
~Rule();
Rule& top_left(match_fn match);
Rule& top_middle(match_fn match);
Rule& top_right(match_fn match);
Rule& middle_left(match_fn match);
Rule& middle_right(match_fn match);
Rule& bottom_left(match_fn match);
Rule& bottom_middle(match_fn match);
Rule& bottom_right(match_fn match);
private:
Rule(RulesBuilder& builder, uint16_t source, uint16_t to);
RulesBuilder& builder;
uint16_t source;
uint16_t to;
match_fn matches[8];
}; // class RuleBuilder
class Sand
{
friend RulesBuilder;
public:
void set(uint16_t x, uint16_t y, const std::string& type);
uint16_t get(uint16_t x, uint16_t y);
void update();
private:
Sand(const RulesBuilder& builder, uint16_t width, uint16_t height,
const std::string& initial);
uint16_t apply_rule(uint16_t tile, unsigned __int128 key) const;
static unsigned __int128 apply_mask(unsigned __int128 key, uint8_t mask);
uint16_t width;
uint16_t height;
type_map types;
std::unordered_map<uint16_t, uint16_t> default_conversions;
std::unordered_map<uint16_t,
std::vector<std::pair<unsigned __int128, uint16_t>>>
rules;
std::vector<uint16_t> elements;
bool iter;
}; // class Sand
} // namespace sand
#endif // SAND_H

52
include/sand/rule.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef SAND_RULE_H
#define SAND_RULE_H
#include "sand/type.h"
#include <functional>
namespace sand
{
class rule
{
public:
class builder;
using match_function = std::function<bool(type)>;
struct metadata
{
uint32_t begin;
uint32_t end;
};
using mask = uint32_t;
rule(const rule&) = delete;
rule(rule&&) = default;
rule& operator=(const rule&) = delete;
~rule();
rule& top_left(match_function match);
rule& top(match_function match);
rule& top_right(match_function match);
rule& left(match_function match);
rule& right(match_function match);
rule& bottom_left(match_function match);
rule& bottom(match_function match);
rule& bottom_right(match_function match);
private:
rule(builder& rb, type from, type to);
builder& rb;
type from;
type to;
match_function matches[8];
friend builder;
};
}
#endif // SAND_RULE_H

View File

@ -0,0 +1,36 @@
#ifndef SAND_RULE_BUILDER_H
#define SAND_RULE_BUILDER_H
#include "sand/rule.h"
#include "sand/type_range.h"
namespace sand
{
class sand;
class rule::builder
{
public:
rule add_rule(type from, type to);
sand build(uint16_t width, uint16_t height, type initial);
private:
builder(type::range types, const std::vector<type>& default_conversions);
void finish_rule(const rule& rule);
unsigned int get_mask_index(type from, type to);
unsigned int insert_conversion(type from, type to);
type::range types;
std::vector<type> conversions;
std::vector<metadata> metas;
std::vector<mask> masks;
friend rule;
friend type::builder;
};
}
#endif // SAND_RULE_BUILDER_H

43
include/sand/sand.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef SAND_H
#define SAND_H
#include "sand/rule.h"
#include "sand/rule_builder.h"
#include "sand/type.h"
#include "sand/type_builder.h"
#include "sand/type_range.h"
#include <vector>
namespace sand
{
class sand
{
public:
type get(int x, int y) const;
void set(int x, int y, type type);
void tick();
private:
sand(type::range types, const std::vector<type>& conversions,
const std::vector<rule::metadata>& metas,
const std::vector<rule::mask>& masks, int width, int height,
type initial);
type::range types;
std::vector<type> conversions;
std::vector<rule::metadata> metas;
std::vector<rule::mask> masks;
int width;
int height;
std::vector<type> data;
bool current;
friend class rule::builder;
};
}
#endif // SAND_H

39
include/sand/type.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef SAND_TYPE_H
#define SAND_TYPE_H
#include <cstdint>
namespace sand
{
class rule;
class sand;
class type
{
public:
class builder;
class range;
bool operator==(const type&) const;
static const type OFF_GRID;
static constexpr unsigned int MAX_TYPES = UINT16_MAX;
private:
using id_ty = uint16_t;
type(id_ty id);
operator id_ty() const;
id_ty id;
friend builder;
friend range;
friend rule;
friend sand;
};
}
#endif // SAND_TYPE_H

View File

@ -0,0 +1,31 @@
#ifndef SAND_TYPE_BUILDER_H
#define SAND_TYPE_BUILDER_H
#include "sand/rule.h"
#include "sand/type.h"
#include <vector>
namespace sand
{
class type::builder
{
public:
builder();
type assign();
range assign_range(unsigned int count);
void set_default_conversion(const type& to, const type& from);
rule::builder build();
private:
id_ty next_id;
std::vector<type> default_conversions;
};
}
#endif // SAND_TYPE_BUILDER_H

49
include/sand/type_range.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef SAND_TYPE_RANGE_H
#define SAND_TYPE_RANGE_H
#include "sand/type.h"
namespace sand
{
class type::range
{
public:
class iter;
iter begin() const;
iter end() const;
bool has(const type&) const;
uint16_t size() const;
private:
explicit range(id_ty begin, id_ty end);
id_ty _begin;
id_ty _end;
friend builder;
};
class type::range::iter
{
public:
type operator*() const;
iter& operator++();
iter& operator--();
bool operator!=(const iter&) const;
private:
iter(id_ty value);
id_ty value;
friend range;
};
}
#endif // SAND_TYPE_RANGE_H

View File

@ -5,112 +5,70 @@
#include <SDL3/SDL_surface.h>
#include <SDL3/SDL_timer.h>
#include <SDL3/SDL_video.h>
#include <sand.h>
#include <tracy/Tracy.hpp>
#include <sand/sand.h>
constexpr uint16_t WIDTH = 128;
constexpr uint16_t HEIGHT = 128;
#ifdef TRACY_ENABLE
#include <tracy/Tracy.hpp>
#endif
constexpr uint16_t WIDTH = 256;
constexpr uint16_t HEIGHT = 256;
constexpr int WINDOW_WIDTH = 1024;
constexpr int WINDOW_HEIGHT = 1024;
struct color
{
uint8_t c[3];
uint8_t r, g, b;
};
color lookup_color(uint16_t id)
{
if (id == 0)
{
return { 0, 0, 0 };
}
else if (id == 1)
{
return { 150, 150, 255 };
}
else
{
color c;
c.c[0] = 257 - id;
c.c[1] = 257 - id;
c.c[2] = 257 - id;
return c;
}
}
int main(int argc, char** argv)
{
auto tb = sand::TypeBuilder().add_type("air");
auto tb = sand::type::builder();
auto air = tb.assign();
auto sand1 = tb.assign();
auto sand2 = tb.assign();
for (int i = 0; i < 255; i++)
tb.set_default_conversion(sand1, sand2);
tb.set_default_conversion(sand2, sand1);
auto rb = tb.build();
rb.add_rule(air, sand2).top([=](auto t) { return t == sand1; });
rb.add_rule(air, sand1).top([=](auto t) { return t == sand2; });
rb.add_rule(sand1, air).bottom([=](auto t) { return t == air; });
rb.add_rule(sand2, air).bottom([=](auto t) { return t == air; });
rb.add_rule(air, sand1)
.top_right([=](auto t) { return t == sand2; })
.right([=](auto t) { return t != air && t != sand::type::OFF_GRID; });
rb.add_rule(air, sand2)
.top_left([=](auto t) { return t == sand1; })
.left([=](auto t) { return t != air && t != sand::type::OFF_GRID; });
rb.add_rule(sand1, air)
.bottom_right([=](auto t) { return t == air; })
.bottom([=](auto t) { return t != air && t != sand::type::OFF_GRID; });
rb.add_rule(sand2, air)
.bottom_left([=](auto t) { return t == air; })
.bottom([=](auto t) { return t != air && t != sand::type::OFF_GRID; });
auto s = rb.build(WIDTH, HEIGHT, air);
const auto& cf = [=](sand::type t) -> color
{
tb.add_type("stone" + std::to_string(i), "stone" + std::to_string(i + 1));
}
auto rb = tb.finish();
rb.add_rule("air", "stone0")
.bottom_middle([](const std::string& t) { return t == "offgrid"; })
.top_middle([](const std::string& t) { return t == "air"; });
for (int i = 0; i < 128; i++)
{
auto ar = rb.add_rule("air", "stone" + std::to_string(i + 1));
auto afn
= [i](const std::string& t) { return t == "stone" + std::to_string(i); };
auto sr = rb.add_rule("stone" + std::to_string(i), "air");
auto sfn = [](const std::string& t) { return t == "air"; };
switch (i % 3)
if (t == air)
{
case 0:
ar.bottom_left(afn);
sr.top_right(sfn);
break;
case 1:
ar.bottom_middle(afn);
sr.top_middle(sfn);
break;
case 2:
ar.bottom_right(afn);
sr.top_left(sfn);
break;
return { 0x40, 0x60, 0x80 };
}
}
for (int i = 128; i < 255; i++)
{
auto ar = rb.add_rule("air", "stone" + std::to_string(i + 1));
auto afn
= [i](const std::string& t) { return t == "stone" + std::to_string(i); };
auto sr = rb.add_rule("stone" + std::to_string(i), "air");
auto sfn = [](const std::string& t) { return t == "air"; };
switch (i % 3)
else if (t == sand1 || t == sand2)
{
case 0:
ar.top_left(afn);
sr.bottom_right(sfn);
break;
case 1:
ar.top_middle(afn);
sr.bottom_middle(sfn);
break;
case 2:
ar.top_right(afn);
sr.bottom_left(sfn);
break;
return { 0xB0, 0x90, 0x50 };
}
}
auto s = rb.build(WIDTH, HEIGHT, "air");
else
{
return { 0, 0, 0 };
}
};
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window
@ -136,11 +94,16 @@ int main(int argc, char** argv)
float dt = (ms - prev_ms) / 1000.0f;
prev_ms = ms;
time += dt;
SDL_Log("%f\n", dt);
float x, y;
int state;
time = 0;
s.update();
if (time > 0)
{
time = 0;
s.tick();
}
if ((state = SDL_GetMouseState(&x, &y)))
{
@ -149,7 +112,7 @@ int main(int argc, char** argv)
if (ix >= 0 && ix < WIDTH && iy >= 0 && iy < HEIGHT)
{
s.set(ix, iy, "stone0");
s.set(ix, iy, sand1);
}
}
@ -158,8 +121,10 @@ int main(int argc, char** argv)
{
for (int j = 0; j < HEIGHT; j++)
{
memcpy(&static_cast<uint8_t*>(surface->pixels)[3 * (i + j * WIDTH)],
lookup_color(s.get(i, j)).c, 3);
color c = cf(s.get(i, j));
static_cast<uint8_t*>(surface->pixels)[3 * (i + j * WIDTH) + 0] = c.r;
static_cast<uint8_t*>(surface->pixels)[3 * (i + j * WIDTH) + 1] = c.g;
static_cast<uint8_t*>(surface->pixels)[3 * (i + j * WIDTH) + 2] = c.b;
}
}
SDL_UnlockSurface(surface);

63
src/rule.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "sand/rule.h"
#include "sand/rule_builder.h"
using namespace sand;
rule::~rule() { rb.finish_rule(*this); }
rule& rule::top_left(rule::match_function match)
{
matches[0] = match;
return *this;
}
rule& rule::top(rule::match_function match)
{
matches[1] = match;
return *this;
}
rule& rule::top_right(rule::match_function match)
{
matches[2] = match;
return *this;
}
rule& rule::left(rule::match_function match)
{
matches[3] = match;
return *this;
}
rule& rule::right(rule::match_function match)
{
matches[4] = match;
return *this;
}
rule& rule::bottom_left(rule::match_function match)
{
matches[5] = match;
return *this;
}
rule& rule::bottom(rule::match_function match)
{
matches[6] = match;
return *this;
}
rule& rule::bottom_right(rule::match_function match)
{
matches[7] = match;
return *this;
}
rule::rule(rule::builder& rb, type from, type to) :
rb(rb),
from(from),
to(to),
matches{ nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr }
{
}

95
src/rule_builder.cpp Normal file
View File

@ -0,0 +1,95 @@
#include "sand/rule_builder.h"
#include "sand/sand.h"
using namespace sand;
// -------------------
// rules datastructure
// -------------------
// `conversions` - possible result types of a rule
// `metas` - refers to slices of `conversions` for a
// specific tile type
// `masks` - bitmask that represents which conversions are
// *invalid* for given tile types (to, from) and a neighbor position
rule rule::builder::add_rule(type from, type to)
{
return rule(*this, from, to);
}
::sand::sand rule::builder::build(uint16_t width, uint16_t height,
type initial)
{
return ::sand::sand(types, conversions, metas, masks, width, height,
initial);
}
rule::builder::builder(type::range types,
const std::vector<type>& default_conversions) :
types(types),
conversions(default_conversions),
metas(types.size()),
masks(types.size() * types.size() * 8, -1U)
{
for (const auto& type : types)
{
metas[type] = { .begin = type, .end = type + 1u };
}
}
void rule::builder::finish_rule(const rule& rule)
{
unsigned int position = insert_conversion(rule.from, rule.to);
for (int i = 0; i < 8; i++)
{
// not wildcard
// ------------
// include `position` only for matching types
if (rule.matches[i])
{
for (const auto& type : types)
{
if (rule.matches[i](type))
{
// mask out default conversion
masks[i + type * 8 + rule.from * types.size() * 8] &= ~(1 << 0);
}
else
{
// mask out rule.to from
masks[i + type * 8 + rule.from * types.size() * 8]
&= ~(1 << position);
}
}
}
}
}
unsigned int rule::builder::get_mask_index(type from, type to)
{
for (int i = metas[from].begin; i < metas[from].end; i++)
{
if (conversions[i] == to)
{
return i;
}
}
return insert_conversion(from, to);
}
unsigned int rule::builder::insert_conversion(type from, type to)
{
uint32_t position = metas[from].end;
metas[from].end += 1;
conversions.insert(conversions.begin() + position, to);
for (auto& meta : metas)
{
if (meta.begin >= position)
{
meta.begin += 1;
meta.end += 1;
}
}
return position - metas[from].begin;
}

View File

@ -1,529 +1,80 @@
#include "sand.h"
#include "sand/sand.h"
#include <algorithm>
#include <compare>
#include <cstdint>
#include <tracy/Tracy.hpp>
#include "sand/type.h"
using namespace sand;
namespace
sand::type sand::sand::get(int x, int y) const
{
constexpr uint16_t OFFGRID_VALUE = 0;
template <typename T>
int binary_search(T element, const T* elements, size_t begin, size_t end,
auto (*fn)(T, T)->std::strong_ordering)
{
if (end - begin == 1)
if (x < 0 || x >= width || y < 0 || y >= height)
{
if (fn(element, elements[begin]) == std::strong_ordering::equal)
return type::OFF_GRID;
}
return data[x + y * width + current * width * height];
}
void sand::sand::set(int x, int y, type type)
{
data[x + y * width + current * width * height] = type;
}
void sand::sand::tick()
{
#pragma omp parallel for
for (int i = 0; i < width * height; i++)
{
const int x = i % width;
const int y = i / width;
auto [begin, end] = metas[get(x, y)];
uint32_t mask = -1U;
int iter = 0;
for (int dy = -1; dy <= 1; dy++)
{
return begin;
}
else
{
return -1;
}
}
size_t index = begin + (end - begin) / 2;
auto comparison_result = fn(element, elements[index]);
if (comparison_result == std::strong_ordering::less)
{
return binary_search<T>(element, elements, begin, index, fn);
}
if (comparison_result == std::strong_ordering::greater)
{
return binary_search<T>(element, elements, index, end, fn);
}
return index;
}
} // namespace
TypeBuilder::TypeBuilder() :
next_type_id(OFFGRID_VALUE)
{
add_type("offgrid");
}
TypeBuilder& TypeBuilder::add_type(const std::string& name)
{
if (!types.contains(name))
{
types[name] = { .id = next_type_id, .default_result = next_type_id };
next_type_id += 1;
}
return *this;
}
TypeBuilder& TypeBuilder::add_type(const std::string& name,
const std::string& default_result)
{
add_type(name);
add_type(default_result);
types[name].default_result = types[default_result].id;
return *this;
}
RulesBuilder TypeBuilder::finish() { return RulesBuilder(*this); }
Rule RulesBuilder::add_rule(const std::string& name, const std::string& to)
{
return Rule(*this, types[name].id, types[to].id);
}
Sand RulesBuilder::build(uint16_t width, uint16_t height,
const std::string& initial)
{
for (auto& [key, rule] : rules)
{
std::sort(rule.begin(), rule.end(),
[](auto a, auto b) { return a.first < b.first; });
}
return Sand(*this, width, height, initial);
}
RulesBuilder::RulesBuilder(const TypeBuilder& type_builder) :
types(type_builder.types)
{
}
void RulesBuilder::finish_rule(const Rule& rule)
{
// 8 nested loops is kinda silly, maybe there's a better way to do this?
// top left
for (const auto& [tl_name, tl_type] : types)
{
if (!rule.matches[0] || rule.matches[0](tl_name))
{
// top middle
for (const auto& [tm_name, tm_type] : types)
for (int dx = -1; dx <= 1; dx++)
{
if (!rule.matches[1] || rule.matches[1](tm_name))
if (dx == 0 && dy == 0)
{
// top right
for (const auto& [tr_name, tr_type] : types)
{
if (!rule.matches[2] || rule.matches[2](tr_name))
{
// middle left
for (const auto& [ml_name, ml_type] : types)
{
if (!rule.matches[3] || rule.matches[3](ml_name))
{
// middle right
for (const auto& [mr_name, mr_type] : types)
{
if (!rule.matches[4] || rule.matches[4](mr_name))
{
// bottom left
for (const auto& [bl_name, bl_type] : types)
{
if (!rule.matches[5] || rule.matches[5](bl_name))
{
// bottom middle
for (const auto& [bm_name, bm_type] : types)
{
if (!rule.matches[6] || rule.matches[6](bm_name))
{
// bottom right
for (const auto& [br_name, br_type] : types)
{
if (!rule.matches[7]
|| rule.matches[7](br_name))
{
unsigned __int128 neighbors = 0;
if (rule.matches[0])
{
neighbors += tl_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16;
if (rule.matches[1])
{
neighbors += tm_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16;
if (rule.matches[2])
{
neighbors += tr_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16;
if (rule.matches[3])
{
neighbors += ml_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16;
if (rule.matches[4])
{
neighbors += mr_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16;
if (rule.matches[5])
{
neighbors += bl_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16;
if (rule.matches[6])
{
neighbors += bm_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16;
if (rule.matches[7])
{
neighbors += br_type.id;
}
else
{
neighbors += 0xFFFF;
}
if (!rules.contains(rule.source))
{
rules[rule.source] = {};
}
rules[rule.source].push_back(
{ neighbors, rule.to });
}
if (!rule.matches[7])
{
break;
}
} // bottom right
}
if (!rule.matches[6])
{
break;
}
} // bottom middle
}
if (!rule.matches[5])
{
break;
}
} // bottom left
}
if (!rule.matches[4])
{
break;
}
} // middle right
}
if (!rule.matches[3])
{
break;
}
} // middle left
}
if (!rule.matches[2])
{
break;
}
} // top right
continue;
}
if (!rule.matches[1])
{
break;
}
} // top middle
}
mask &= masks[iter + get(x + dx, y + dy) * 8
+ get(x, y) * types.size() * 8];
if (!rule.matches[0])
{
break;
}
} // top left
}
Rule::~Rule() { builder.finish_rule(*this); }
Rule& Rule::top_left(match_fn match)
{
matches[0] = match;
return *this;
}
Rule& Rule::top_middle(match_fn match)
{
matches[1] = match;
return *this;
}
Rule& Rule::top_right(match_fn match)
{
matches[2] = match;
return *this;
}
Rule& Rule::middle_left(match_fn match)
{
matches[3] = match;
return *this;
}
Rule& Rule::middle_right(match_fn match)
{
matches[4] = match;
return *this;
}
Rule& Rule::bottom_left(match_fn match)
{
matches[5] = match;
return *this;
}
Rule& Rule::bottom_middle(match_fn match)
{
matches[6] = match;
return *this;
}
Rule& Rule::bottom_right(match_fn match)
{
matches[7] = match;
return *this;
}
Rule::Rule(RulesBuilder& builder, uint16_t source, uint16_t to) :
builder(builder),
source(source),
to(to),
matches{}
{
}
Sand::Sand(const RulesBuilder& builder, uint16_t width, uint16_t height,
const std::string& initial) :
width(width),
height(height),
types(builder.types),
rules(builder.rules),
elements(width * height * 2, types[initial].id),
iter(false)
{
for (const auto& [name, type] : types)
{
default_conversions[type.id] = type.default_result;
}
int cnt = 0;
for (const auto& [key, elem] : rules)
{
cnt += elem.size();
}
}
void Sand::set(uint16_t x, uint16_t y, const std::string& type)
{
elements[x + y * width + iter * width * height] = types[type].id;
}
uint16_t Sand::get(uint16_t x, uint16_t y)
{
return elements[x + y * width + iter * width * height];
}
void Sand::update()
{
ZoneScoped;
int n;
#pragma omp parallel for private(n)
for (n = 0; n < width * height; n++)
{
int i = n % width;
int j = n / height;
unsigned __int128 neighbors = 0;
if (i > 0 && j > 0)
{
neighbors += get(i - 1, j - 1);
}
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16;
if (j > 0)
{
neighbors += get(i, j - 1);
}
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16;
if (i < width - 1 && j > 0)
{
neighbors += get(i + 1, j - 1);
}
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16;
if (i > 0)
{
neighbors += get(i - 1, j);
}
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16;
if (i < width - 1)
{
neighbors += get(i + 1, j);
}
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16;
if (i > 0 && j < height - 1)
{
neighbors += get(i - 1, j + 1);
}
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16;
if (j < height - 1)
{
neighbors += get(i, j + 1);
}
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16;
if (i < width - 1 && j < height - 1)
{
neighbors += get(i + 1, j + 1);
}
else
{
neighbors += OFFGRID_VALUE;
}
elements[i + j * width + !iter * width * height]
= apply_rule(get(i, j), neighbors);
}
iter = !iter;
}
uint16_t Sand::apply_rule(uint16_t tile, unsigned __int128 key) const
{
if (rules.contains(tile))
{
auto& rules_for_tile = rules.at(tile);
if (!rules_for_tile.empty())
{
for (int mask = 0; mask < 256; mask++)
{
std::pair<unsigned __int128, uint16_t> element
= { apply_mask(key, mask), 0 };
std::strong_ordering (*fn)(decltype(element), decltype(element))
= [](auto a, auto b) { return a.first <=> b.first; };
auto found = binary_search(element, rules_for_tile.data(), 0,
rules_for_tile.size(), fn);
if (found >= 0)
{
return rules_for_tile[found].second;
}
iter += 1;
}
}
}
return default_conversions.at(tile);
}
unsigned __int128 Sand::apply_mask(unsigned __int128 key, uint8_t mask)
{
unsigned __int128 any = 0xFFFF;
for (int i = 0; i < 8; i++)
{
if (mask & 1)
bool found = false;
for (int bit = begin; bit < end; bit++)
{
key |= any;
if (mask & 1)
{
data[x + y * width + !current * width * height] = conversions[bit];
found = true;
break;
}
mask >>= 1;
}
if (!found)
{
data[x + y * width + !current * width * height] = conversions[begin];
}
mask >>= 1;
any <<= 16;
}
return key;
current = !current;
}
sand::sand::sand(type::range types, const std::vector<type>& conversions,
const std::vector<rule::metadata>& metas,
const std::vector<rule::mask>& masks, int width, int height,
type initial) :
types(types),
conversions(conversions),
metas(metas),
masks(masks),
width(width),
height(height),
data(width * height * 2, initial),
current(false)
{
}

23
src/sand.hip Normal file
View File

@ -0,0 +1,23 @@
#include <hip/amd_detail/amd_hip_runtime.h>
#include <hip/driver_types.h>
#include <hip/hip_runtime.h>
#include <iostream>
#include <numeric>
#include <vector>
#define HIP_CHECK(condition) \
do \
{ \
const hipError_t error = condition; \
if (error != hipSuccess) \
{ \
std::cerr << "An error occured: \"" << hipGetErrorString(error) \
<< "\t at " << __FILE__ << ":" << __LINE__ << std::endl; \
std::exit(-1); \
} \
} while (false)
template <typename T> constexpr T ceildiv(const T& a, const T& b)
{
return (a + b - 1) / b;
}

13
src/type.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "sand/type.h"
using namespace sand;
const type type::OFF_GRID = 0;
bool type::operator==(const type& other) const { return id == other.id; }
type::type(id_ty id) :
id(id)
{
}
type::operator id_ty() const { return id; }

40
src/type_builder.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "sand/type_builder.h"
#include "sand/rule_builder.h"
#include "sand/type_range.h"
#include <cassert>
using namespace sand;
type::builder::builder() :
// 0 is a special ID
next_id(1),
default_conversions{ OFF_GRID }
{
}
type type::builder::assign()
{
default_conversions.push_back(next_id);
return next_id++;
}
type::range type::builder::assign_range(unsigned int count)
{
assert(count > 0 && "count must be greater than 0");
type::range r(next_id, next_id + count);
next_id += count;
return r;
}
void type::builder::set_default_conversion(const type& to, const type& from)
{
default_conversions[to] = from;
}
rule::builder type::builder::build()
{
return rule::builder(type::range(0, next_id), default_conversions);
}

42
src/type_range.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "sand/type_range.h"
using namespace sand;
type::range::iter type::range::begin() const { return _begin; }
type::range::iter type::range::end() const { return _end; }
bool type::range::has(const type& type) const
{
return type.id >= _begin && type.id < _end;
}
uint16_t type::range::size() const { return _end - _begin; }
type::range::range(type::id_ty begin, type::id_ty end) :
_begin(begin),
_end(end)
{
}
type type::range::iter::operator*() const { return value; }
type::range::iter& type::range::iter::operator++()
{
value++;
return *this;
}
type::range::iter& type::range::iter::operator--()
{
value--;
return *this;
}
bool type::range::iter::operator!=(const type::range::iter& other) const
{
return value != other.value;
}
type::range::iter::iter(type::id_ty value) :
value(value)
{
}