Improve(?) wildcard rules

This commit is contained in:
shylie 2026-04-21 18:31:38 -04:00
parent ec5c6b2bec
commit f6c0e07489
6 changed files with 352 additions and 87 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "third/tracy"]
path = third/tracy
url = https://github.com/wolfpld/tracy.git

View File

@ -22,6 +22,16 @@ set_target_properties(sand PROPERTIES
CXX_STANDARD_REQUIRED ON CXX_STANDARD_REQUIRED ON
) )
if (EXISTS third/tracy/CMakeLists.txt)
option(TRACY_ENABLE "Enable Tracy" ON)
set(TRACY_STATIC OFF)
set(TRACY_ON_DEMAND ON)
add_subdirectory(third/tracy)
target_link_libraries(sand PUBLIC Tracy::TracyClient)
else()
target_compile_definitions(sand PUBLIC FrameMark ZoneScoped)
endif()
find_package(SDL3 CONFIG REQUIRED) find_package(SDL3 CONFIG REQUIRED)
add_executable(sandtest add_executable(sandtest

View File

@ -2,6 +2,7 @@
#define SAND_H #define SAND_H
#include <cstdint> #include <cstdint>
#include <functional>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -22,6 +23,8 @@ class TypeBuilder
friend RulesBuilder; friend RulesBuilder;
public: public:
TypeBuilder();
TypeBuilder& add_type(const std::string& name); TypeBuilder& add_type(const std::string& name);
TypeBuilder& add_type(const std::string& name, TypeBuilder& add_type(const std::string& name,
const std::string& default_result); const std::string& default_result);
@ -62,7 +65,7 @@ class Rule
friend RulesBuilder; friend RulesBuilder;
public: public:
using match_fn = bool (*)(const std::string& name); using match_fn = std::function<bool(const std::string&)>;
~Rule(); ~Rule();
@ -98,6 +101,10 @@ private:
Sand(const RulesBuilder& builder, uint16_t width, uint16_t height, Sand(const RulesBuilder& builder, uint16_t width, uint16_t height,
const std::string& initial); 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 width;
uint16_t height; uint16_t height;
type_map types; type_map types;

View File

@ -6,47 +6,109 @@
#include <SDL3/SDL_timer.h> #include <SDL3/SDL_timer.h>
#include <SDL3/SDL_video.h> #include <SDL3/SDL_video.h>
#include <sand.h> #include <sand.h>
#include <tracy/Tracy.hpp>
constexpr uint16_t WIDTH = 256; constexpr uint16_t WIDTH = 128;
constexpr uint16_t HEIGHT = 256; constexpr uint16_t HEIGHT = 128;
constexpr int WINDOW_WIDTH = 1024; constexpr int WINDOW_WIDTH = 1024;
constexpr int WINDOW_HEIGHT = 1024; constexpr int WINDOW_HEIGHT = 1024;
uint8_t lookup[][3] struct color
= { { 0, 0, 0 }, { 50, 0, 0 }, { 100, 100, 100 }, { 100, 100, 100 } }; {
uint8_t c[3];
};
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) int main(int argc, char** argv)
{ {
auto rb = sand::TypeBuilder() auto tb = sand::TypeBuilder().add_type("air");
.add_type("offgrid")
.add_type("air")
.add_type("stone1", "stone2")
.add_type("stone2", "stone1")
.finish();
rb.add_rule("air", "stone2") for (int i = 0; i < 255; i++)
.top_middle([](const auto& t) { return t == "stone1"; }); {
rb.add_rule("air", "stone1") tb.add_type("stone" + std::to_string(i), "stone" + std::to_string(i + 1));
.top_middle([](const auto& t) { return t == "stone2"; }); }
rb.add_rule("air", "stone2")
.top_right([](const auto& t) { return t == "stone1"; })
.middle_right([](const auto& t) { return t != "air"; });
rb.add_rule("air", "stone1")
.top_left([](const auto& t) { return t == "stone2"; })
.middle_left([](const auto& t) { return t != "air"; });
rb.add_rule("stone1", "air") auto rb = tb.finish();
.bottom_middle([](const auto& t) { return t == "air"; });
rb.add_rule("stone1", "air")
.bottom_left([](const auto& t) { return t == "air"; })
.bottom_middle([](const auto& t) { return t != "air"; });
rb.add_rule("stone2", "air") rb.add_rule("air", "stone0")
.bottom_middle([](const auto& t) { return t == "air"; }); .bottom_middle([](const std::string& t) { return t == "offgrid"; })
rb.add_rule("stone2", "air") .top_middle([](const std::string& t) { return t == "air"; });
.bottom_right([](const auto& t) { return t == "air"; })
.bottom_middle([](const auto& 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)
{
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;
}
}
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)
{
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;
}
}
auto s = rb.build(WIDTH, HEIGHT, "air"); auto s = rb.build(WIDTH, HEIGHT, "air");
@ -77,8 +139,6 @@ int main(int argc, char** argv)
float x, y; float x, y;
int state; int state;
if (time > 1 / 1000.0f)
{
time = 0; time = 0;
s.update(); s.update();
@ -89,8 +149,7 @@ int main(int argc, char** argv)
if (ix >= 0 && ix < WIDTH && iy >= 0 && iy < HEIGHT) if (ix >= 0 && ix < WIDTH && iy >= 0 && iy < HEIGHT)
{ {
s.set(ix, iy, state == 1 ? "stone1" : "stone2"); s.set(ix, iy, "stone0");
}
} }
} }
@ -100,13 +159,15 @@ int main(int argc, char** argv)
for (int j = 0; j < HEIGHT; j++) for (int j = 0; j < HEIGHT; j++)
{ {
memcpy(&static_cast<uint8_t*>(surface->pixels)[3 * (i + j * WIDTH)], memcpy(&static_cast<uint8_t*>(surface->pixels)[3 * (i + j * WIDTH)],
lookup[s.get(i, j)], 3); lookup_color(s.get(i, j)).c, 3);
} }
} }
SDL_UnlockSurface(surface); SDL_UnlockSurface(surface);
SDL_BlitSurfaceScaled(surface, nullptr, SDL_GetWindowSurface(window), SDL_BlitSurfaceScaled(surface, nullptr, SDL_GetWindowSurface(window),
nullptr, SDL_SCALEMODE_NEAREST); nullptr, SDL_SCALEMODE_NEAREST);
SDL_UpdateWindowSurface(window); SDL_UpdateWindowSurface(window);
FrameMark;
} }
SDL_DestroySurface(surface); SDL_DestroySurface(surface);

View File

@ -1,40 +1,59 @@
#include "sand.h" #include "sand.h"
#include <algorithm> #include <algorithm>
#include <compare>
#include <cstdint> #include <cstdint>
#include <tracy/Tracy.hpp>
using namespace sand; using namespace sand;
namespace namespace
{ {
constexpr uint16_t OFFGRID_VALUE = 0;
template <typename T> template <typename T>
T& binary_search(T element, T* elements, size_t begin, size_t end, int binary_search(T element, const T* elements, size_t begin, size_t end,
auto (*fn)(T, T)->bool) auto (*fn)(T, T)->std::strong_ordering)
{ {
if (end - begin == 1) if (end - begin == 1)
{ {
return elements[begin]; if (fn(element, elements[begin]) == std::strong_ordering::equal)
{
return begin;
}
else
{
return -1;
}
} }
size_t index = begin + (end - begin) / 2; size_t index = begin + (end - begin) / 2;
if (!fn(element, elements[index]) && !fn(elements[index], element)) auto comparison_result = fn(element, elements[index]);
{
return elements[index]; if (comparison_result == std::strong_ordering::less)
}
else if (fn(element, elements[index]))
{
return binary_search<T>(element, elements, index, end, fn);
}
else
{ {
return binary_search<T>(element, elements, begin, index, fn); 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 } // namespace
TypeBuilder::TypeBuilder() :
next_type_id(OFFGRID_VALUE)
{
add_type("offgrid");
}
TypeBuilder& TypeBuilder::add_type(const std::string& name) TypeBuilder& TypeBuilder::add_type(const std::string& name)
{ {
if (!types.contains(name)) if (!types.contains(name))
@ -126,28 +145,84 @@ void RulesBuilder::finish_rule(const Rule& rule)
{ {
unsigned __int128 neighbors = 0; unsigned __int128 neighbors = 0;
if (rule.matches[0])
{
neighbors += tl_type.id; neighbors += tl_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16; neighbors <<= 16;
if (rule.matches[1])
{
neighbors += tm_type.id; neighbors += tm_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16; neighbors <<= 16;
if (rule.matches[2])
{
neighbors += tr_type.id; neighbors += tr_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16; neighbors <<= 16;
if (rule.matches[3])
{
neighbors += ml_type.id; neighbors += ml_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16; neighbors <<= 16;
if (rule.matches[4])
{
neighbors += mr_type.id; neighbors += mr_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16; neighbors <<= 16;
if (rule.matches[5])
{
neighbors += bl_type.id; neighbors += bl_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16; neighbors <<= 16;
if (rule.matches[6])
{
neighbors += bm_type.id; neighbors += bm_type.id;
}
else
{
neighbors += 0xFFFF;
}
neighbors <<= 16; neighbors <<= 16;
if (rule.matches[7])
{
neighbors += br_type.id; neighbors += br_type.id;
}
else
{
neighbors += 0xFFFF;
}
if (!rules.contains(rule.source)) if (!rules.contains(rule.source))
{ {
@ -157,21 +232,61 @@ void RulesBuilder::finish_rule(const Rule& rule)
rules[rule.source].push_back( rules[rule.source].push_back(
{ neighbors, rule.to }); { 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
} }
if (!rule.matches[1])
{
break;
} }
} // top middle
} }
if (!rule.matches[0])
{
break;
} }
} // top left
} }
Rule::~Rule() { builder.finish_rule(*this); } Rule::~Rule() { builder.finish_rule(*this); }
@ -253,6 +368,12 @@ Sand::Sand(const RulesBuilder& builder, uint16_t width, uint16_t height,
{ {
default_conversions[type.id] = type.default_result; 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) void Sand::set(uint16_t x, uint16_t y, const std::string& type)
@ -267,6 +388,8 @@ uint16_t Sand::get(uint16_t x, uint16_t y)
void Sand::update() void Sand::update()
{ {
ZoneScoped;
int n; int n;
#pragma omp parallel for private(n) #pragma omp parallel for private(n)
for (n = 0; n < width * height; n++) for (n = 0; n < width * height; n++)
@ -274,73 +397,133 @@ void Sand::update()
int i = n % width; int i = n % width;
int j = n / height; int j = n / height;
auto& current_rule = rules[get(i, j)];
unsigned __int128 neighbors = 0; unsigned __int128 neighbors = 0;
if (i > 0 && j > 0) if (i > 0 && j > 0)
{ {
neighbors += get(i - 1, j - 1); neighbors += get(i - 1, j - 1);
} }
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16; neighbors <<= 16;
if (j > 0) if (j > 0)
{ {
neighbors += get(i, j - 1); neighbors += get(i, j - 1);
} }
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16; neighbors <<= 16;
if (i < width - 1 && j > 0) if (i < width - 1 && j > 0)
{ {
neighbors += get(i + 1, j - 1); neighbors += get(i + 1, j - 1);
} }
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16; neighbors <<= 16;
if (i > 0) if (i > 0)
{ {
neighbors += get(i - 1, j); neighbors += get(i - 1, j);
} }
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16; neighbors <<= 16;
if (i < width - 1) if (i < width - 1)
{ {
neighbors += get(i + 1, j); neighbors += get(i + 1, j);
} }
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16; neighbors <<= 16;
if (i > 0 && j < height - 1) if (i > 0 && j < height - 1)
{ {
neighbors += get(i - 1, j + 1); neighbors += get(i - 1, j + 1);
} }
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16; neighbors <<= 16;
if (j < height - 1) if (j < height - 1)
{ {
neighbors += get(i, j + 1); neighbors += get(i, j + 1);
} }
else
{
neighbors += OFFGRID_VALUE;
}
neighbors <<= 16; neighbors <<= 16;
if (i < width - 1 && j < height - 1) if (i < width - 1 && j < height - 1)
{ {
neighbors += get(i + 1, j + 1); neighbors += get(i + 1, j + 1);
} }
else
uint16_t result = default_conversions[get(i, j)];
if (!current_rule.empty())
{ {
std::pair<unsigned __int128, uint16_t> element = { neighbors, 0 }; neighbors += OFFGRID_VALUE;
bool (*fn)(decltype(element), decltype(element))
= [](decltype(element) a, decltype(element) b) -> bool
{ return a.first > b.first; };
const auto& found = binary_search(element, current_rule.data(), 0,
current_rule.size(), fn);
if (found.first == neighbors)
{
result = found.second;
}
} }
elements[i + j * width + !iter * width * height] = result; elements[i + j * width + !iter * width * height]
= apply_rule(get(i, j), neighbors);
} }
iter = !iter; 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;
}
}
}
}
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)
{
key |= any;
}
mask >>= 1;
any <<= 16;
}
return key;
}

1
third/tracy Submodule

@ -0,0 +1 @@
Subproject commit 25f09bee2cdb680d025d150188925e116fc1e8d4