auto unhandled(P&&... p) -> void {
+ return _unhandled({forward(p)...});
+ }
+
+ template auto unimplemented(P&&... p) -> void {
+ return _unimplemented({forward(p)...});
+ }
+
+ template auto unusual(P&&... p) -> void {
+ return _unusual({forward(p)...});
+ }
+
+ template auto unverified(P&&... p) -> void {
+ return _unverified({forward(p)...});
+ }
+
+private:
+ auto _unhandled(const string&) -> void;
+ auto _unimplemented(const string&) -> void;
+ auto _unusual(const string&) -> void;
+ auto _unverified(const string&) -> void;
+
+ u64 _totalNotices = 0;
+ vector _unhandledNotices;
+ vector _unimplementedNotices;
+ vector _unusualNotices;
+ vector _unverifiedNotices;
+};
+
+extern Debug _debug;
+
+}
+
+#define debug(function, ...) if constexpr(1) ares::_debug.function(__VA_ARGS__)
diff --git a/waterbox/ares64/ares/ares/ares/inline.hpp b/waterbox/ares64/ares/ares/ares/inline.hpp
new file mode 100644
index 0000000000..f72a69893d
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/inline.hpp
@@ -0,0 +1,4 @@
+#include
+#include
+#include
+#include
diff --git a/waterbox/ares64/ares/ares/ares/memory/fixed-allocator.cpp b/waterbox/ares64/ares/ares/ares/memory/fixed-allocator.cpp
new file mode 100644
index 0000000000..cb3a7384e8
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/memory/fixed-allocator.cpp
@@ -0,0 +1,22 @@
+#include
+
+namespace ares::Memory {
+
+#if defined(PLATFORM_MACOS) && defined(ARCHITECTURE_ARM64)
+//stub for unsupported platforms
+FixedAllocator::FixedAllocator() {
+}
+#else
+alignas(4096) u8 fixedBuffer[128_MiB];
+
+FixedAllocator::FixedAllocator() {
+ _allocator.resize(sizeof(fixedBuffer), 0, fixedBuffer);
+}
+#endif
+
+auto FixedAllocator::get() -> bump_allocator& {
+ static FixedAllocator allocator;
+ return allocator._allocator;
+}
+
+}
diff --git a/waterbox/ares64/ares/ares/ares/memory/fixed-allocator.hpp b/waterbox/ares64/ares/ares/ares/memory/fixed-allocator.hpp
new file mode 100644
index 0000000000..09d8d85e44
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/memory/fixed-allocator.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+namespace ares::Memory {
+
+struct FixedAllocator {
+ static auto get() -> bump_allocator&;
+
+private:
+ FixedAllocator();
+
+ bump_allocator _allocator;
+};
+
+}
diff --git a/waterbox/ares64/ares/ares/ares/memory/memory.hpp b/waterbox/ares64/ares/ares/ares/memory/memory.hpp
new file mode 100644
index 0000000000..9d2ea3c5cf
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/memory/memory.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+namespace ares::Memory {
+
+inline auto mirror(u32 address, u32 size) -> u32 {
+ if(size == 0) return 0;
+ u32 base = 0;
+ u32 mask = 1 << 31;
+ while(address >= size) {
+ while(!(address & mask)) mask >>= 1;
+ address -= mask;
+ if(size > mask) {
+ size -= mask;
+ base += mask;
+ }
+ mask >>= 1;
+ }
+ return base + address;
+}
+
+inline auto reduce(u32 address, u32 mask) -> u32 {
+ while(mask) {
+ u32 bits = (mask & -mask) - 1;
+ address = address >> 1 & ~bits | address & bits;
+ mask = (mask & mask - 1) >> 1;
+ }
+ return address;
+}
+
+}
diff --git a/waterbox/ares64/ares/ares/ares/memory/readable.hpp b/waterbox/ares64/ares/ares/ares/memory/readable.hpp
new file mode 100644
index 0000000000..23dae3494d
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/memory/readable.hpp
@@ -0,0 +1,73 @@
+#pragma once
+
+#include
+
+namespace ares::Memory {
+
+template
+struct Readable {
+ ~Readable() { reset(); }
+
+ auto reset() -> void {
+ delete[] self.data;
+ self.data = nullptr;
+ self.size = 0;
+ self.mask = 0;
+ }
+
+ auto allocate(u32 size, T fill = (T)~0ull) -> void {
+ if(!size) return reset();
+ delete[] self.data;
+ self.size = size;
+ self.mask = bit::round(self.size) - 1;
+ self.data = new T[self.mask + 1];
+ memory::fill(self.data, self.mask + 1, fill);
+ }
+
+ auto fill(T fill = ~0ull) -> void {
+ for(u32 address : range(self.size)) {
+ self.data[address] = fill;
+ }
+ }
+
+ auto load(VFS::File fp) -> void {
+ if(!self.size) allocate(fp->size());
+ fp->read({self.data, min(fp->size(), self.size * sizeof(T))});
+ for(u32 address = self.size; address <= self.mask; address++) {
+ self.data[address] = self.data[mirror(address, self.size)];
+ }
+ }
+
+ auto save(VFS::File fp) -> void {
+ fp->write({self.data, min(fp->size(), self.size * sizeof(T))});
+ }
+
+ explicit operator bool() const { return (bool)self.data; }
+ auto data() const -> const T* { return self.data; }
+ auto size() const -> u32 { return self.size; }
+ auto mask() const -> u32 { return self.mask; }
+
+ auto operator[](u32 address) const -> T { return self.data[address & self.mask]; }
+ auto read(u32 address) const -> T { return self.data[address & self.mask]; }
+ auto write(u32 address, T data) const -> void {}
+ auto program(u32 address, T data) const -> void { self.data[address & self.mask] = data; }
+
+ auto begin() -> T* { return &self.data[0]; }
+ auto end() -> T* { return &self.data[self.size]; }
+
+ auto begin() const -> const T* { return &self.data[0]; }
+ auto end() const -> const T* { return &self.data[self.size]; }
+
+ auto serialize(serializer& s) -> void {
+ s(array_span{self.data, self.size});
+ }
+
+//private:
+ struct {
+ T* data = nullptr;
+ u32 size = 0;
+ u32 mask = 0;
+ } self;
+};
+
+}
diff --git a/waterbox/ares64/ares/ares/ares/memory/writable.hpp b/waterbox/ares64/ares/ares/ares/memory/writable.hpp
new file mode 100644
index 0000000000..ebde9ebc70
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/memory/writable.hpp
@@ -0,0 +1,75 @@
+#pragma once
+
+#include
+
+namespace ares::Memory {
+
+template
+struct Writable {
+ ~Writable() { reset(); }
+
+ auto reset() -> void {
+ delete[] self.data;
+ self.data = nullptr;
+ self.size = 0;
+ self.mask = 0;
+ }
+
+ auto allocate(u32 size, T fill = (T)~0ull) -> void {
+ if(!size) return reset();
+ delete[] self.data;
+ self.size = size;
+ self.mask = bit::round(self.size) - 1;
+ self.data = new T[self.mask + 1];
+ memory::fill(self.data, self.mask + 1, fill);
+ }
+
+ auto fill(T fill = ~0ull) -> void {
+ for(u32 address : range(self.size)) {
+ self.data[address] = fill;
+ }
+ }
+
+ auto load(VFS::File fp) -> void {
+ if(!self.size) allocate(fp->size());
+ fp->read({self.data, min(fp->size(), self.size * sizeof(T))});
+ for(u32 address = self.size; address <= self.mask; address++) {
+ self.data[address] = self.data[mirror(address, self.size)];
+ }
+ }
+
+ auto save(VFS::File fp) -> void {
+ fp->write({self.data, min(fp->size(), self.size * sizeof(T))});
+ }
+
+ explicit operator bool() const { return (bool)self.data; }
+ auto data() -> T* { return self.data; }
+ auto data() const -> const T* { return self.data; }
+ auto size() const -> u32 { return self.size; }
+ auto mask() const -> u32 { return self.mask; }
+
+ auto operator[](u32 address) -> T& { return self.data[address & self.mask]; }
+ auto operator[](u32 address) const -> T { return self.data[address & self.mask]; }
+ auto read(u32 address) const -> T { return self.data[address & self.mask]; }
+ auto write(u32 address, T data) -> void { self.data[address & self.mask] = data; }
+ auto program(u32 address, T data) -> void { self.data[address & self.mask] = data; }
+
+ auto begin() -> T* { return &self.data[0]; }
+ auto end() -> T* { return &self.data[self.size]; }
+
+ auto begin() const -> const T* { return &self.data[0]; }
+ auto end() const -> const T* { return &self.data[self.size]; }
+
+ auto serialize(serializer& s) -> void {
+ s(array_span{self.data, self.size});
+ }
+
+private:
+ struct {
+ T* data = nullptr;
+ u32 size = 0;
+ u32 mask = 0;
+ } self;
+};
+
+}
diff --git a/waterbox/ares64/ares/ares/ares/node/attribute.hpp b/waterbox/ares64/ares/ares/ares/node/attribute.hpp
new file mode 100644
index 0000000000..6a88d1d5c1
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/attribute.hpp
@@ -0,0 +1,8 @@
+struct Attribute {
+ Attribute(const string& name, const any& value = {}) : name(name), value(value) {}
+ auto operator==(const Attribute& source) const -> bool { return name == source.name; }
+ auto operator< (const Attribute& source) const -> bool { return name < source.name; }
+
+ string name;
+ any value;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/audio/audio.hpp b/waterbox/ares64/ares/ares/ares/node/audio/audio.hpp
new file mode 100644
index 0000000000..4f34b18658
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/audio/audio.hpp
@@ -0,0 +1,4 @@
+struct Audio : Object {
+ DeclareClass(Audio, "audio")
+ using Object::Object;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/audio/stream.cpp b/waterbox/ares64/ares/ares/ares/node/audio/stream.cpp
new file mode 100644
index 0000000000..88ad3bf702
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/audio/stream.cpp
@@ -0,0 +1,131 @@
+auto Stream::setChannels(u32 channels) -> void {
+ _channels.reset();
+ _channels.resize(channels);
+}
+
+auto Stream::setFrequency(f64 frequency) -> void {
+ _frequency = frequency;
+ setResamplerFrequency(_resamplerFrequency);
+}
+
+auto Stream::setResamplerFrequency(f64 resamplerFrequency) -> void {
+ _resamplerFrequency = resamplerFrequency;
+
+ for(auto& channel : _channels) {
+ channel.nyquist.reset();
+ channel.resampler.reset(_frequency, _resamplerFrequency);
+ }
+
+ if(_frequency >= _resamplerFrequency * 2) {
+ //add a low-pass filter to prevent aliasing during resampling
+ f64 cutoffFrequency = min(25000.0, _resamplerFrequency / 2.0 - 2000.0);
+ for(auto& channel : _channels) {
+ u32 passes = 3;
+ for(u32 pass : range(passes)) {
+ DSP::IIR::Biquad filter;
+ f64 q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
+ filter.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, _frequency, q);
+ channel.nyquist.append(filter);
+ }
+ }
+ }
+}
+
+auto Stream::setMuted(bool muted) -> void {
+ _muted = muted;
+}
+
+auto Stream::resetFilters() -> void {
+ for(auto& channel : _channels) {
+ channel.filters.reset();
+ }
+}
+
+auto Stream::addLowPassFilter(f64 cutoffFrequency, u32 order, u32 passes) -> void {
+ for(auto& channel : _channels) {
+ for(u32 pass : range(passes)) {
+ if(order == 1) {
+ Filter filter{Filter::Mode::OnePole, Filter::Type::LowPass, Filter::Order::First};
+ filter.onePole.reset(DSP::IIR::OnePole::Type::LowPass, cutoffFrequency, _frequency);
+ channel.filters.append(filter);
+ }
+ if(order == 2) {
+ Filter filter{Filter::Mode::Biquad, Filter::Type::LowPass, Filter::Order::Second};
+ f64 q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
+ filter.biquad.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, _frequency, q);
+ channel.filters.append(filter);
+ }
+ }
+ }
+}
+
+auto Stream::addHighPassFilter(f64 cutoffFrequency, u32 order, u32 passes) -> void {
+ for(auto& channel : _channels) {
+ for(u32 pass : range(passes)) {
+ if(order == 1) {
+ Filter filter{Filter::Mode::OnePole, Filter::Type::HighPass, Filter::Order::First};
+ filter.onePole.reset(DSP::IIR::OnePole::Type::HighPass, cutoffFrequency, _frequency);
+ channel.filters.append(filter);
+ }
+ if(order == 2) {
+ Filter filter{Filter::Mode::Biquad, Filter::Type::HighPass, Filter::Order::Second};
+ f64 q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
+ filter.biquad.reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, _frequency, q);
+ channel.filters.append(filter);
+ }
+ }
+ }
+}
+
+auto Stream::addLowShelfFilter(f64 cutoffFrequency, u32 order, f64 gain, f64 slope) -> void {
+ for(auto& channel : _channels) {
+ if(order == 2) {
+ Filter filter{Filter::Mode::Biquad, Filter::Type::LowShelf, Filter::Order::Second};
+ f64 q = DSP::IIR::Biquad::shelf(gain, slope);
+ filter.biquad.reset(DSP::IIR::Biquad::Type::LowShelf, cutoffFrequency, _frequency, q);
+ channel.filters.append(filter);
+ }
+ }
+}
+
+auto Stream::addHighShelfFilter(f64 cutoffFrequency, u32 order, f64 gain, f64 slope) -> void {
+ for(auto& channel : _channels) {
+ if(order == 2) {
+ Filter filter{Filter::Mode::Biquad, Filter::Type::HighShelf, Filter::Order::Second};
+ f64 q = DSP::IIR::Biquad::shelf(gain, slope);
+ filter.biquad.reset(DSP::IIR::Biquad::Type::HighShelf, cutoffFrequency, _frequency, q);
+ channel.filters.append(filter);
+ }
+ }
+}
+
+auto Stream::pending() const -> bool {
+ return _channels && _channels[0].resampler.pending();
+}
+
+auto Stream::read(f64 samples[]) -> u32 {
+ for(u32 c : range(_channels.size())) {
+ samples[c] = _channels[c].resampler.read() * !muted();
+ }
+ return _channels.size();
+}
+
+auto Stream::write(const f64 samples[]) -> void {
+ for(u32 c : range(_channels.size())) {
+ f64 sample = samples[c] + 1e-25; //constant offset used to suppress denormals
+ for(auto& filter : _channels[c].filters) {
+ switch(filter.mode) {
+ case Filter::Mode::OnePole: sample = filter.onePole.process(sample); break;
+ case Filter::Mode::Biquad: sample = filter.biquad.process(sample); break;
+ }
+ }
+ for(auto& filter : _channels[c].nyquist) {
+ sample = filter.process(sample);
+ }
+ _channels[c].resampler.write(sample);
+ }
+
+ //if there are samples pending, then alert the frontend to possibly process them.
+ //this will generally happen when every audio stream has pending samples to be mixed.
+ if(pending()) platform->audio(shared());
+}
diff --git a/waterbox/ares64/ares/ares/ares/node/audio/stream.hpp b/waterbox/ares64/ares/ares/ares/node/audio/stream.hpp
new file mode 100644
index 0000000000..366593b7fc
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/audio/stream.hpp
@@ -0,0 +1,49 @@
+struct Stream : Audio {
+ DeclareClass(Stream, "audio.stream")
+ using Audio::Audio;
+
+ auto channels() const -> u32 { return _channels.size(); }
+ auto frequency() const -> f64 { return _frequency; }
+ auto resamplerFrequency() const -> f64 { return _resamplerFrequency; }
+ auto muted() const -> bool { return _muted; }
+
+ auto setChannels(u32 channels) -> void;
+ auto setFrequency(f64 frequency) -> void;
+ auto setResamplerFrequency(f64 resamplerFrequency) -> void;
+ auto setMuted(bool muted) -> void;
+
+ auto resetFilters() -> void;
+ auto addLowPassFilter(f64 cutoffFrequency, u32 order, u32 passes = 1) -> void;
+ auto addHighPassFilter(f64 cutoffFrequency, u32 order, u32 passes = 1) -> void;
+ auto addLowShelfFilter(f64 cutoffFrequency, u32 order, f64 gain, f64 slope) -> void;
+ auto addHighShelfFilter(f64 cutoffFrequency, u32 order, f64 gain, f64 slope) -> void;
+
+ auto pending() const -> bool;
+ auto read(f64 samples[]) -> u32;
+ auto write(const f64 samples[]) -> void;
+
+ template
+ auto frame(P&&... p) -> void {
+ if(runAhead()) return;
+ f64 samples[sizeof...(p)] = {forward(p)...};
+ write(samples);
+ }
+
+protected:
+ struct Filter {
+ enum class Mode : u32 { OnePole, Biquad } mode;
+ enum class Type : u32 { None, LowPass, HighPass, LowShelf, HighShelf } type;
+ enum class Order : u32 { None, First, Second } order;
+ DSP::IIR::OnePole onePole;
+ DSP::IIR::Biquad biquad;
+ };
+ struct Channel {
+ vector filters;
+ vector nyquist;
+ DSP::Resampler::Cubic resampler;
+ };
+ vector _channels;
+ f64 _frequency = 48000.0;
+ f64 _resamplerFrequency = 48000.0;
+ bool _muted = false;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/class.hpp b/waterbox/ares64/ares/ares/ares/node/class.hpp
new file mode 100644
index 0000000000..b171cdcf3c
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/class.hpp
@@ -0,0 +1,34 @@
+//horrible implementation of run-time introspection:
+//allow converting a unique class string to a derived Node type.
+
+struct Class {
+ struct Instance {
+ const string identifier;
+ const function create;
+ };
+
+ static auto classes() -> vector& {
+ static vector classes;
+ return classes;
+ }
+
+ template static auto register() -> void {
+ if(!classes().find([&](auto instance) { return instance.identifier == T::identifier(); })) {
+ classes().append({T::identifier(), &T::create});
+ } else {
+ throw;
+ }
+ }
+
+ static auto create(string identifier) -> Node::Object {
+ if(auto index = classes().find([&](auto instance) { return instance.identifier == identifier; })) {
+ return classes()[*index].create();
+ }
+ if(identifier == "Object") throw; //should never occur: detects unregistered classes
+ return create("Object");
+ }
+
+ template struct Register {
+ Register() { Class::register(); }
+ };
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/component/component.hpp b/waterbox/ares64/ares/ares/ares/node/component/component.hpp
new file mode 100644
index 0000000000..edee5c3ff4
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/component/component.hpp
@@ -0,0 +1,4 @@
+struct Component : Object {
+ DeclareClass(Component, "component");
+ using Object::Object;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/component/real-time-clock.hpp b/waterbox/ares64/ares/ares/ares/node/component/real-time-clock.hpp
new file mode 100644
index 0000000000..c9395f46eb
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/component/real-time-clock.hpp
@@ -0,0 +1,30 @@
+struct RealTimeClock : Component {
+ DeclareClass(RealTimeClock, "component.real-time-clock")
+ using Component::Component;
+
+ auto update() -> void { if(_update) return _update(); }
+ auto timestamp() const -> u64 { return _timestamp; }
+
+ auto setUpdate(function update) -> void { _update = update; }
+ auto setTimestamp(u64 timestamp) -> void { _timestamp = timestamp; }
+
+ auto synchronize(u64 timestamp = 0) -> void {
+ if(!timestamp) timestamp = chrono::timestamp();
+ _timestamp = timestamp;
+ update();
+ }
+
+ auto serialize(string& output, string depth) -> void override {
+ Component::serialize(output, depth);
+ output.append(depth, " timestamp: ", _timestamp, "\n");
+ }
+
+ auto unserialize(Markup::Node node) -> void override {
+ Component::unserialize(node);
+ _timestamp = node["timestamp"].natural();
+ }
+
+protected:
+ function _update;
+ u64 _timestamp;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/debugger/debugger.hpp b/waterbox/ares64/ares/ares/ares/node/debugger/debugger.hpp
new file mode 100644
index 0000000000..a5b5954a70
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/debugger/debugger.hpp
@@ -0,0 +1,4 @@
+struct Debugger : Object {
+ DeclareClass(Debugger, "debugger")
+ using Object::Object;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/debugger/graphics.hpp b/waterbox/ares64/ares/ares/ares/node/debugger/graphics.hpp
new file mode 100644
index 0000000000..d3a46c7a82
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/debugger/graphics.hpp
@@ -0,0 +1,26 @@
+struct Graphics : Debugger {
+ DeclareClass(Graphics, "debugger.graphics")
+
+ Graphics(string name = {}) : Debugger(name) {
+ }
+
+ auto width() const -> u32 { return _width; }
+ auto height() const -> u32 { return _height; }
+ auto capture() const -> vector { if(_capture) return _capture(); return {}; }
+
+ auto setSize(u32 width, u32 height) -> void { _width = width, _height = height; }
+ auto setCapture(function ()> capture) -> void { _capture = capture; }
+
+ auto serialize(string& output, string depth) -> void override {
+ Debugger::serialize(output, depth);
+ }
+
+ auto unserialize(Markup::Node node) -> void override {
+ Debugger::unserialize(node);
+ }
+
+protected:
+ u32 _width = 0;
+ u32 _height = 0;
+ function ()> _capture;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/debugger/memory.hpp b/waterbox/ares64/ares/ares/ares/node/debugger/memory.hpp
new file mode 100644
index 0000000000..3b46a1094e
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/debugger/memory.hpp
@@ -0,0 +1,27 @@
+struct Memory : Debugger {
+ DeclareClass(Memory, "debugger.memory")
+
+ Memory(string name = {}) : Debugger(name) {
+ }
+
+ auto size() const -> u32 { return _size; }
+ auto read(u32 address) const -> n8 { if(_read) return _read(address); return 0; }
+ auto write(u32 address, u8 data) const -> void { if(_write) return _write(address, data); }
+
+ auto setSize(u32 size) -> void { _size = size; }
+ auto setRead(function read) -> void { _read = read; }
+ auto setWrite(function write) -> void { _write = write; }
+
+ auto serialize(string& output, string depth) -> void override {
+ Debugger::serialize(output, depth);
+ }
+
+ auto unserialize(Markup::Node node) -> void override {
+ Debugger::unserialize(node);
+ }
+
+protected:
+ u32 _size = 0;
+ function _read;
+ function _write;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/debugger/properties.hpp b/waterbox/ares64/ares/ares/ares/node/debugger/properties.hpp
new file mode 100644
index 0000000000..9c85950cde
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/debugger/properties.hpp
@@ -0,0 +1,21 @@
+struct Properties : Debugger {
+ DeclareClass(Properties, "debugger.properties")
+
+ Properties(string name = {}) : Debugger(name) {
+ }
+
+ auto query() const -> string { if(_query) return _query(); return {}; }
+
+ auto setQuery(function query) -> void { _query = query; }
+
+ auto serialize(string& output, string depth) -> void override {
+ Debugger::serialize(output, depth);
+ }
+
+ auto unserialize(Markup::Node node) -> void override {
+ Debugger::unserialize(node);
+ }
+
+protected:
+ function _query;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/debugger/tracer/instruction.hpp b/waterbox/ares64/ares/ares/ares/node/debugger/tracer/instruction.hpp
new file mode 100644
index 0000000000..3f530bfc66
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/debugger/tracer/instruction.hpp
@@ -0,0 +1,125 @@
+struct Instruction : Tracer {
+ DeclareClass(Instruction, "debugger.tracer.instruction")
+
+ Instruction(string name = {}, string component = {}) : Tracer(name, component) {
+ setMask(_mask);
+ setDepth(_depth);
+ }
+
+ auto addressBits() const -> u32 { return _addressBits; }
+ auto addressMask() const -> u32 { return _addressMask; }
+ auto mask() const -> bool { return _mask; }
+ auto depth() const -> u32 { return _depth; }
+
+ auto setAddressBits(u32 addressBits, u32 addressMask = 0) -> void {
+ _addressBits = addressBits;
+ _addressMask = addressMask;
+ }
+
+ auto setMask(bool mask) -> void {
+ _mask = mask;
+ }
+
+ auto setDepth(u32 depth) -> void {
+ _depth = depth;
+ _history.reset();
+ _history.resize(depth);
+ for(auto& history : _history) history = ~0;
+ }
+
+ auto address(u32 address) -> bool {
+ address &= (1ull << _addressBits) - 1; //mask upper bits of address
+ _address = address;
+ address >>= _addressMask; //clip unneeded alignment bits (to reduce _masks size)
+
+ if(_mask && updateMasks()) {
+ if(_masks[address >> 3] & 1 << (address & 7)) return false; //do not trace twice
+ _masks[address >> 3] |= 1 << (address & 7);
+ }
+
+ if(_depth) {
+ for(auto history : _history) {
+ if(_address == history) {
+ _omitted++;
+ return false; //do not trace again if recently traced
+ }
+ }
+ for(auto index : range(_depth - 1)) {
+ _history[index] = _history[index + 1];
+ }
+ _history.last() = _address;
+ }
+
+ return true;
+ }
+
+ //mark an already-executed address as not executed yet for trace masking.
+ //call when writing to executable RAM to support self-modifying code.
+ auto invalidate(u32 address) -> void {
+ if(unlikely(_mask && updateMasks())) {
+ address &= (1ull << _addressBits) - 1;
+ address >>= _addressMask;
+ _masks[address >> 3] &= ~(1 << (address & 7));
+ }
+ }
+
+ auto notify(const string& instruction, const string& context, const string& extra = {}) -> void {
+ if(!enabled()) return;
+
+ if(_omitted) {
+ PlatformLog({
+ "[Omitted: ", _omitted, "]\n"}
+ );
+ _omitted = 0;
+ }
+
+ string output{
+ _component, " ",
+ hex(_address, _addressBits + 3 >> 2), " ",
+ instruction, " ",
+ context, " ",
+ extra
+ };
+ PlatformLog({output.strip(), "\n"});
+ }
+
+ auto serialize(string& output, string depth) -> void override {
+ Tracer::serialize(output, depth);
+ output.append(depth, " addressBits: ", _addressBits, "\n");
+ output.append(depth, " addressMask: ", _addressMask, "\n");
+ output.append(depth, " mask: ", _mask, "\n");
+ output.append(depth, " depth: ", _depth, "\n");
+ }
+
+ auto unserialize(Markup::Node node) -> void override {
+ Tracer::unserialize(node);
+ _addressBits = node["addressBits"].natural();
+ _addressMask = node["addressMask"].natural();
+ _mask = node["mask"].boolean();
+ _depth = node["depth"].natural();
+
+ setMask(_mask);
+ setDepth(_depth);
+ }
+
+protected:
+ auto updateMasks() -> bool {
+ auto size = 1ull << _addressBits >> _addressMask >> 3;
+ if(!_mask || !size) return _masks.reset(), false;
+ if(_masks.size() == size) return true;
+ _masks.reset();
+ _masks.resize(size);
+ return true;
+ }
+
+ u32 _addressBits = 32;
+ u32 _addressMask = 0;
+ bool _mask = false;
+ u32 _depth = 4;
+
+//unserialized:
+ n64 _address = 0;
+ n64 _omitted = 0;
+ vector _history;
+ vector _masks;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/debugger/tracer/notification.hpp b/waterbox/ares64/ares/ares/ares/node/debugger/tracer/notification.hpp
new file mode 100644
index 0000000000..a9f7331d38
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/debugger/tracer/notification.hpp
@@ -0,0 +1,18 @@
+struct Notification : Tracer {
+ DeclareClass(Notification, "debugger.tracer.notification")
+
+ Notification(string name = {}, string component = {}) : Tracer(name, component) {
+ }
+
+ auto notify(const string& message = {}) -> void {
+ if(!enabled()) return;
+
+ if(message) {
+ PlatformLog({_component, " ", _name, ": ", message, "\n"});
+ } else {
+ PlatformLog({_component, " ", _name, "\n"});
+ }
+ }
+
+protected:
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/debugger/tracer/tracer.hpp b/waterbox/ares64/ares/ares/ares/node/debugger/tracer/tracer.hpp
new file mode 100644
index 0000000000..82f38e201a
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/debugger/tracer/tracer.hpp
@@ -0,0 +1,29 @@
+struct Tracer : Debugger {
+ DeclareClass(Tracer, "debugger.tracer")
+
+ Tracer(string name = {}, string component = {}) : Debugger(name) {
+ _component = component;
+ }
+
+ auto component() const -> string { return _component; }
+ auto enabled() const -> bool { return _enabled; }
+
+ auto setComponent(string component) -> void { _component = component; }
+ auto setEnabled(bool enabled) -> void { _enabled = enabled; }
+
+ auto serialize(string& output, string depth) -> void override {
+ Debugger::serialize(output, depth);
+ output.append(depth, " component: ", _component, "\n");
+ output.append(depth, " enabled: ", _enabled, "\n");
+ }
+
+ auto unserialize(Markup::Node node) -> void override {
+ Debugger::unserialize(node);
+ _component = node["component"].string();
+ _enabled = node["enabled"].boolean();
+ }
+
+protected:
+ string _component;
+ bool _enabled = false;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/input/axis.hpp b/waterbox/ares64/ares/ares/ares/node/input/axis.hpp
new file mode 100644
index 0000000000..866fc5eb4e
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/input/axis.hpp
@@ -0,0 +1,12 @@
+struct Axis : Input {
+ DeclareClass(Axis, "input.axis")
+ using Input::Input;
+
+ auto value() const -> s64 { return _value; }
+ auto setValue(s64 value) -> void { _value = value; }
+
+protected:
+ s64 _value = 0;
+ s64 _minimum = -32768;
+ s64 _maximum = +32767;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/input/button.hpp b/waterbox/ares64/ares/ares/ares/node/input/button.hpp
new file mode 100644
index 0000000000..deb1a88d93
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/input/button.hpp
@@ -0,0 +1,10 @@
+struct Button : Input {
+ DeclareClass(Button, "input.button")
+ using Input::Input;
+
+ auto value() const -> bool { return _value; }
+ auto setValue(bool value) -> void { _value = value; }
+
+protected:
+ bool _value = 0;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/input/input.hpp b/waterbox/ares64/ares/ares/ares/node/input/input.hpp
new file mode 100644
index 0000000000..04ef9f4126
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/input/input.hpp
@@ -0,0 +1,4 @@
+struct Input : Object {
+ DeclareClass(Input, "input")
+ using Object::Object;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/input/rumble.hpp b/waterbox/ares64/ares/ares/ares/node/input/rumble.hpp
new file mode 100644
index 0000000000..cad47ebd0a
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/input/rumble.hpp
@@ -0,0 +1,10 @@
+struct Rumble : Input {
+ DeclareClass(Rumble, "input.rumble")
+ using Input::Input;
+
+ auto enable() const -> bool { return _enable; }
+ auto setEnable(bool enable) -> void { _enable = enable; }
+
+protected:
+ bool _enable = 0;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/input/trigger.hpp b/waterbox/ares64/ares/ares/ares/node/input/trigger.hpp
new file mode 100644
index 0000000000..6a092439bd
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/input/trigger.hpp
@@ -0,0 +1,12 @@
+struct Trigger : Input {
+ DeclareClass(Trigger, "input.trigger")
+ using Input::Input;
+
+ auto value() const -> s64 { return _value; }
+ auto setValue(s64 value) -> void { _value = value; }
+
+protected:
+ s64 _value = 0;
+ s64 _minimum = 0;
+ s64 _maximum = +32767;
+};
diff --git a/waterbox/ares64/ares/ares/ares/node/node.cpp b/waterbox/ares64/ares/ares/ares/node/node.cpp
new file mode 100644
index 0000000000..c8b96ef7f0
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/node.cpp
@@ -0,0 +1,9 @@
+namespace ares::Core {
+ namespace Video {
+ #include
+ #include
+ }
+ namespace Audio {
+ #include
+ }
+}
diff --git a/waterbox/ares64/ares/ares/ares/node/node.hpp b/waterbox/ares64/ares/ares/ares/node/node.hpp
new file mode 100644
index 0000000000..e91ed880d4
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/node.hpp
@@ -0,0 +1,185 @@
+namespace ares::Core {
+ struct Object;
+ struct System;
+ struct Peripheral;
+ struct Port;
+ namespace Component {
+ struct Component;
+ struct RealTimeClock;
+ }
+ namespace Video {
+ struct Video;
+ struct Sprite;
+ struct Screen;
+ }
+ namespace Audio {
+ struct Audio;
+ struct Stream;
+ }
+ namespace Input {
+ struct Input;
+ struct Button;
+ struct Axis;
+ struct Trigger;
+ struct Rumble;
+ }
+ namespace Setting {
+ struct Setting;
+ struct Boolean;
+ struct Natural;
+ struct Integer;
+ struct Real;
+ struct String;
+ }
+ namespace Debugger {
+ struct Debugger;
+ struct Memory;
+ struct Graphics;
+ struct Properties;
+ namespace Tracer {
+ struct Tracer;
+ struct Notification;
+ struct Instruction;
+ }
+ }
+}
+
+namespace ares::Node {
+ using Object = shared_pointer;
+ using System = shared_pointer;
+ using Peripheral = shared_pointer;
+ using Port = shared_pointer;
+ namespace Component {
+ using Component = shared_pointer;
+ using RealTimeClock = shared_pointer;
+ }
+ namespace Video {
+ using Video = shared_pointer;
+ using Sprite = shared_pointer;
+ using Screen = shared_pointer;
+ }
+ namespace Audio {
+ using Audio = shared_pointer;
+ using Stream = shared_pointer;
+ }
+ namespace Input {
+ using Input = shared_pointer;
+ using Button = shared_pointer;
+ using Axis = shared_pointer;
+ using Trigger = shared_pointer;
+ using Rumble = shared_pointer;
+ }
+ namespace Setting {
+ using Setting = shared_pointer;
+ using Boolean = shared_pointer;
+ using Natural = shared_pointer;
+ using Integer = shared_pointer;
+ using Real = shared_pointer;
+ using String = shared_pointer;
+ }
+ namespace Debugger {
+ using Debugger = shared_pointer;
+ using Memory = shared_pointer;
+ using Graphics = shared_pointer;
+ using Properties = shared_pointer;
+ namespace Tracer {
+ using Tracer = shared_pointer;
+ using Notification = shared_pointer;
+ using Instruction = shared_pointer;
+ }
+ }
+}
+
+namespace ares::Core {
+ // forward declarations
+ static auto PlatformAttach(Node::Object) -> void;
+ static auto PlatformDetach(Node::Object) -> void;
+ static auto PlatformLog(string_view) -> void;
+
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ namespace Component {
+ #include
+ #include
+ }
+ namespace Video {
+ #include
+ #include
+ #include
+ }
+ namespace Audio {
+ #include
+ #include
+ }
+ namespace Input {
+ #include
+ #include
+ #include
+ #include
+ #include
+ }
+ namespace Setting {
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ }
+ namespace Debugger {
+ #include
+ #include
+ #include
+ #include
+ namespace Tracer {
+ #include
+ #include
+ #include
+ }
+ }
+}
+
+namespace ares::Node {
+ static inline auto create(string identifier) -> Object {
+ return Core::Class::create(identifier);
+ }
+
+ static inline auto serialize(Object node) -> string {
+ if(!node) return {};
+ string result;
+ node->serialize(result, {});
+ return result;
+ }
+
+ static inline auto unserialize(string markup) -> Object {
+ auto document = BML::unserialize(markup);
+ if(!document) return {};
+ auto node = Core::Class::create(document["node"].string());
+ node->unserialize(document["node"]);
+ return node;
+ }
+
+ static inline auto parent(Object child) -> Object {
+ if(!child || !child->parent()) return {};
+ if(auto parent = child->parent().acquire()) return parent;
+ return {};
+ }
+
+ template
+ static inline auto find(Object from, string name) -> Object {
+ if(!from) return {};
+ if(auto object = from->find(name)) return object;
+ return {};
+ }
+
+ template
+ static inline auto enumerate(Object from) -> vector {
+ vector objects;
+ if(from) from->enumerate(objects);
+ return objects;
+ }
+}
diff --git a/waterbox/ares64/ares/ares/ares/node/object.hpp b/waterbox/ares64/ares/ares/ares/node/object.hpp
new file mode 100644
index 0000000000..5225a404c7
--- /dev/null
+++ b/waterbox/ares64/ares/ares/ares/node/object.hpp
@@ -0,0 +1,235 @@
+//identifier() is static, allowing template to access via T::identifier()
+//identity() is virtual, allowing T* to access via T->identity()
+
+#define DeclareClass(Type, Name) \
+ static auto identifier() -> string { return Name; } \
+ static auto create() -> Node::Object { return new Type; } \
+ auto identity() const -> string override { return Name; } \
+ private: static inline Class::Register register; public: \
+
+struct Object : shared_pointer_this