mirror of
https://github.com/xodio/xod.git
synced 2026-03-14 20:56:52 +01:00
1405 lines
36 KiB
C++
Generated
1405 lines
36 KiB
C++
Generated
// The sketch is auto-generated with XOD (https://xod.io).
|
||
//
|
||
// You can compile and upload it to an Arduino-compatible board with
|
||
// Arduino IDE.
|
||
//
|
||
// Rough code overview:
|
||
//
|
||
// - Configuration section
|
||
// - STL shim
|
||
// - Immutable list classes and functions
|
||
// - XOD runtime environment
|
||
// - Native node implementation
|
||
// - Program graph definition
|
||
//
|
||
// Search for comments fenced with '====' and '----' to navigate through
|
||
// the major code blocks.
|
||
|
||
#include <Arduino.h>
|
||
#include <inttypes.h>
|
||
#include <avr/pgmspace.h>
|
||
|
||
|
||
/*=============================================================================
|
||
*
|
||
*
|
||
* Configuration
|
||
*
|
||
*
|
||
=============================================================================*/
|
||
|
||
// Uncomment to turn on debug of the program
|
||
//#define XOD_DEBUG
|
||
|
||
// Uncomment to trace the program runtime in the Serial Monitor
|
||
//#define XOD_DEBUG_ENABLE_TRACE
|
||
|
||
/*=============================================================================
|
||
*
|
||
*
|
||
* STL shim. Provides implementation for vital std::* constructs
|
||
*
|
||
*
|
||
=============================================================================*/
|
||
|
||
namespace std {
|
||
|
||
template< class T > struct remove_reference {typedef T type;};
|
||
template< class T > struct remove_reference<T&> {typedef T type;};
|
||
template< class T > struct remove_reference<T&&> {typedef T type;};
|
||
|
||
template <class T>
|
||
typename remove_reference<T>::type&& move(T&& a) {
|
||
return static_cast<typename remove_reference<T>::type&&>(a);
|
||
}
|
||
|
||
} // namespace std
|
||
|
||
/*=============================================================================
|
||
*
|
||
*
|
||
* XOD-specific list/array implementations
|
||
*
|
||
*
|
||
=============================================================================*/
|
||
|
||
#ifndef XOD_LIST_H
|
||
#define XOD_LIST_H
|
||
|
||
namespace xod {
|
||
namespace detail {
|
||
|
||
/*
|
||
* Cursors are used internaly by iterators and list views. They are not exposed
|
||
* directly to a list consumer.
|
||
*
|
||
* The base `Cursor` is an interface which provides the bare minimum of methods
|
||
* to facilitate a single iteration pass.
|
||
*/
|
||
template<typename T> class Cursor {
|
||
public:
|
||
virtual ~Cursor() { }
|
||
virtual bool isValid() const = 0;
|
||
virtual bool value(T* out) const = 0;
|
||
virtual void next() = 0;
|
||
};
|
||
|
||
template<typename T> class NilCursor : public Cursor<T> {
|
||
public:
|
||
virtual bool isValid() const { return false; }
|
||
virtual bool value(T*) const { return false; }
|
||
virtual void next() { }
|
||
};
|
||
|
||
} // namespace detail
|
||
|
||
/*
|
||
* Iterator is an object used to iterate a list once.
|
||
*
|
||
* Users create new iterators by calling `someList.iterate()`.
|
||
* Iterators are created on stack and are supposed to have a
|
||
* short live, e.g. for a duration of `for` loop or node’s
|
||
* `evaluate` function. Iterators can’t be copied.
|
||
*
|
||
* Implemented as a pimpl pattern wrapper over the cursor.
|
||
* Once created for a cursor, an iterator owns that cursor
|
||
* and will delete the cursor object once destroyed itself.
|
||
*/
|
||
template<typename T>
|
||
class Iterator {
|
||
public:
|
||
static Iterator<T> nil() {
|
||
return Iterator<T>(new detail::NilCursor<T>());
|
||
}
|
||
|
||
Iterator(detail::Cursor<T>* cursor)
|
||
: _cursor(cursor)
|
||
{ }
|
||
|
||
~Iterator() {
|
||
if (_cursor)
|
||
delete _cursor;
|
||
}
|
||
|
||
Iterator(const Iterator& that) = delete;
|
||
Iterator& operator=(const Iterator& that) = delete;
|
||
|
||
Iterator(Iterator&& it)
|
||
: _cursor(it._cursor)
|
||
{
|
||
it._cursor = nullptr;
|
||
}
|
||
|
||
Iterator& operator=(Iterator&& it) {
|
||
auto tmp = it._cursor;
|
||
it._cursor = _cursor;
|
||
_cursor = tmp;
|
||
return *this;
|
||
}
|
||
|
||
operator bool() const { return _cursor->isValid(); }
|
||
|
||
bool value(T* out) const {
|
||
return _cursor->value(out);
|
||
}
|
||
|
||
T operator*() const {
|
||
T out;
|
||
_cursor->value(&out);
|
||
return out;
|
||
}
|
||
|
||
Iterator& operator++() {
|
||
_cursor->next();
|
||
return *this;
|
||
}
|
||
|
||
private:
|
||
detail::Cursor<T>* _cursor;
|
||
};
|
||
|
||
/*
|
||
* An interface for a list view. A particular list view provides a new
|
||
* kind of iteration over existing data. This way we can use list slices,
|
||
* list concatenations, list rotations, etc without introducing new data
|
||
* buffers. We just change the way already existing data is iterated.
|
||
*
|
||
* ListView is not exposed to a list user directly, it is used internally
|
||
* by the List class. However, deriving a new ListView is necessary if you
|
||
* make a new list/string processing node.
|
||
*/
|
||
template<typename T> class ListView {
|
||
public:
|
||
virtual Iterator<T> iterate() const = 0;
|
||
};
|
||
|
||
/*
|
||
* The list as it seen by data consumers. Have a single method `iterate`
|
||
* to create a new iterator.
|
||
*
|
||
* Implemented as pimpl pattern wrapper over a list view. Takes pointer
|
||
* to a list view in constructor and expects the view will be alive for
|
||
* the whole life time of the list.
|
||
*/
|
||
template<typename T> class List {
|
||
public:
|
||
constexpr List()
|
||
: _view(nullptr)
|
||
{ }
|
||
|
||
List(const ListView<T>* view)
|
||
: _view(view)
|
||
{ }
|
||
|
||
Iterator<T> iterate() const {
|
||
return _view ? _view->iterate() : Iterator<T>::nil();
|
||
}
|
||
|
||
// pre 0.15.0 backward compatibility
|
||
List* operator->() __attribute__ ((deprecated)) { return this; }
|
||
const List* operator->() const __attribute__ ((deprecated)) { return this; }
|
||
|
||
private:
|
||
const ListView<T>* _view;
|
||
};
|
||
|
||
/*
|
||
* A list view over an old good plain C array.
|
||
*
|
||
* Expects the array will be alive for the whole life time of the
|
||
* view.
|
||
*/
|
||
template<typename T> class PlainListView : public ListView<T> {
|
||
public:
|
||
class Cursor : public detail::Cursor<T> {
|
||
public:
|
||
Cursor(const PlainListView* owner)
|
||
: _owner(owner)
|
||
, _idx(0)
|
||
{ }
|
||
|
||
bool isValid() const override {
|
||
return _idx < _owner->_len;
|
||
}
|
||
|
||
bool value(T* out) const override {
|
||
if (!isValid())
|
||
return false;
|
||
*out = _owner->_data[_idx];
|
||
return true;
|
||
}
|
||
|
||
void next() override { ++_idx; }
|
||
|
||
private:
|
||
const PlainListView* _owner;
|
||
size_t _idx;
|
||
};
|
||
|
||
public:
|
||
PlainListView(const T* data, size_t len)
|
||
: _data(data)
|
||
, _len(len)
|
||
{ }
|
||
|
||
virtual Iterator<T> iterate() const override {
|
||
return Iterator<T>(new Cursor(this));
|
||
}
|
||
|
||
private:
|
||
friend class Cursor;
|
||
const T* _data;
|
||
size_t _len;
|
||
};
|
||
|
||
/*
|
||
* A list view over a null-terminated C-String.
|
||
*
|
||
* Expects the char buffer will be alive for the whole life time of the view.
|
||
* You can use string literals as a buffer, since they are persistent for
|
||
* the program execution time.
|
||
*/
|
||
class CStringView : public ListView<char> {
|
||
public:
|
||
class Cursor : public detail::Cursor<char> {
|
||
public:
|
||
Cursor(const char* str)
|
||
: _ptr(str)
|
||
{ }
|
||
|
||
bool isValid() const override {
|
||
return (bool)*_ptr;
|
||
}
|
||
|
||
bool value(char* out) const override {
|
||
*out = *_ptr;
|
||
return (bool)*_ptr;
|
||
}
|
||
|
||
void next() override { ++_ptr; }
|
||
|
||
private:
|
||
const char* _ptr;
|
||
};
|
||
|
||
public:
|
||
CStringView(const char* str = nullptr)
|
||
: _str(str)
|
||
{ }
|
||
|
||
CStringView& operator=(const CStringView& rhs) {
|
||
_str = rhs._str;
|
||
return *this;
|
||
}
|
||
|
||
virtual Iterator<char> iterate() const override {
|
||
return _str ? Iterator<char>(new Cursor(_str)) : Iterator<char>::nil();
|
||
}
|
||
|
||
private:
|
||
friend class Cursor;
|
||
const char* _str;
|
||
};
|
||
|
||
/*
|
||
* A list view over two other lists (Left and Right) which first iterates the
|
||
* left one, and when exhausted, iterates the right one.
|
||
*
|
||
* Expects both Left and Right to be alive for the whole view life time.
|
||
*/
|
||
template<typename T> class ConcatListView : public ListView<T> {
|
||
public:
|
||
class Cursor : public detail::Cursor<T> {
|
||
public:
|
||
Cursor(Iterator<T>&& left, Iterator<T>&& right)
|
||
: _left(std::move(left))
|
||
, _right(std::move(right))
|
||
{ }
|
||
|
||
bool isValid() const override {
|
||
return _left || _right;
|
||
}
|
||
|
||
bool value(T* out) const override {
|
||
return _left.value(out) || _right.value(out);
|
||
}
|
||
|
||
void next() override {
|
||
_left ? ++_left : ++_right;
|
||
}
|
||
|
||
private:
|
||
Iterator<T> _left;
|
||
Iterator<T> _right;
|
||
};
|
||
|
||
public:
|
||
ConcatListView() { }
|
||
|
||
ConcatListView(List<T> left, List<T> right)
|
||
: _left(left)
|
||
, _right(right)
|
||
{ }
|
||
|
||
ConcatListView& operator=(const ConcatListView& rhs) {
|
||
_left = rhs._left;
|
||
_right = rhs._right;
|
||
return *this;
|
||
}
|
||
|
||
virtual Iterator<T> iterate() const override {
|
||
return Iterator<T>(new Cursor(_left.iterate(), _right.iterate()));
|
||
}
|
||
|
||
private:
|
||
friend class Cursor;
|
||
const List<T> _left;
|
||
const List<T> _right;
|
||
};
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Text string helpers
|
||
//----------------------------------------------------------------------------
|
||
|
||
using XString = List<char>;
|
||
|
||
/*
|
||
* List and list view in a single pack. An utility used to define constant
|
||
* string literals in XOD.
|
||
*/
|
||
class XStringCString : public XString {
|
||
public:
|
||
XStringCString(const char* str)
|
||
: XString(&_view)
|
||
, _view(str)
|
||
{ }
|
||
|
||
private:
|
||
CStringView _view;
|
||
};
|
||
|
||
} // namespace xod
|
||
|
||
#endif
|
||
|
||
/*=============================================================================
|
||
*
|
||
*
|
||
* Basic algorithms for XOD lists
|
||
*
|
||
*
|
||
=============================================================================*/
|
||
|
||
#ifndef XOD_LIST_FUNCS_H
|
||
#define XOD_LIST_FUNCS_H
|
||
|
||
|
||
|
||
namespace xod {
|
||
|
||
/*
|
||
* Folds a list from left. Also known as "reduce".
|
||
*/
|
||
template<typename T, typename TR>
|
||
TR foldl(List<T> xs, TR (*func)(TR, T), TR acc) {
|
||
for (auto it = xs.iterate(); it; ++it)
|
||
acc = func(acc, *it);
|
||
return acc;
|
||
}
|
||
|
||
template<typename T> size_t lengthReducer(size_t len, T) {
|
||
return len + 1;
|
||
}
|
||
|
||
/*
|
||
* Computes length of a list.
|
||
*/
|
||
template<typename T> size_t length(List<T> xs) {
|
||
return foldl(xs, lengthReducer<T>, (size_t)0);
|
||
}
|
||
|
||
template<typename T> T* dumpReducer(T* buff, T x) {
|
||
*buff = x;
|
||
return buff + 1;
|
||
}
|
||
|
||
/*
|
||
* Copies a list content into a memory buffer.
|
||
*
|
||
* It is expected that `outBuff` has enough size to fit all the data.
|
||
*/
|
||
template<typename T> size_t dump(List<T> xs, T* outBuff) {
|
||
T* buffEnd = foldl(xs, dumpReducer, outBuff);
|
||
return buffEnd - outBuff;
|
||
}
|
||
|
||
} // namespace xod
|
||
|
||
#endif
|
||
|
||
|
||
/*=============================================================================
|
||
*
|
||
*
|
||
* Runtime
|
||
*
|
||
*
|
||
=============================================================================*/
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Debug routines
|
||
//----------------------------------------------------------------------------
|
||
#ifndef DEBUG_SERIAL
|
||
# define DEBUG_SERIAL Serial
|
||
#endif
|
||
|
||
#if defined(XOD_DEBUG) && defined(XOD_DEBUG_ENABLE_TRACE)
|
||
# define XOD_TRACE(x) { DEBUG_SERIAL.print(x); DEBUG_SERIAL.flush(); }
|
||
# define XOD_TRACE_LN(x) { DEBUG_SERIAL.println(x); DEBUG_SERIAL.flush(); }
|
||
# define XOD_TRACE_F(x) XOD_TRACE(F(x))
|
||
# define XOD_TRACE_FLN(x) XOD_TRACE_LN(F(x))
|
||
#else
|
||
# define XOD_TRACE(x)
|
||
# define XOD_TRACE_LN(x)
|
||
# define XOD_TRACE_F(x)
|
||
# define XOD_TRACE_FLN(x)
|
||
#endif
|
||
|
||
//----------------------------------------------------------------------------
|
||
// PGM space utilities
|
||
//----------------------------------------------------------------------------
|
||
#define pgm_read_nodeid(address) (pgm_read_word(address))
|
||
|
||
/*
|
||
* Workaround for bugs:
|
||
* https://github.com/arduino/ArduinoCore-sam/pull/43
|
||
* https://github.com/arduino/ArduinoCore-samd/pull/253
|
||
* Remove after the PRs merge
|
||
*/
|
||
#if !defined(ARDUINO_ARCH_AVR) && defined(pgm_read_ptr)
|
||
# undef pgm_read_ptr
|
||
# define pgm_read_ptr(addr) (*(const void **)(addr))
|
||
#endif
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Compatibilities
|
||
//----------------------------------------------------------------------------
|
||
|
||
#if !defined(ARDUINO_ARCH_AVR)
|
||
/*
|
||
* Provide dtostrf function for non-AVR platforms. Although many platforms
|
||
* provide a stub many others do not. And the stub is based on `sprintf`
|
||
* which doesn’t work with floating point formatters on some platforms
|
||
* (e.g. Arduino M0).
|
||
*
|
||
* This is an implementation based on `fcvt` standard function. Taken here:
|
||
* https://forum.arduino.cc/index.php?topic=368720.msg2542614#msg2542614
|
||
*/
|
||
char *dtostrf(double val, int width, unsigned int prec, char *sout) {
|
||
int decpt, sign, reqd, pad;
|
||
const char *s, *e;
|
||
char *p;
|
||
s = fcvt(val, prec, &decpt, &sign);
|
||
if (prec == 0 && decpt == 0) {
|
||
s = (*s < '5') ? "0" : "1";
|
||
reqd = 1;
|
||
} else {
|
||
reqd = strlen(s);
|
||
if (reqd > decpt) reqd++;
|
||
if (decpt == 0) reqd++;
|
||
}
|
||
if (sign) reqd++;
|
||
p = sout;
|
||
e = p + reqd;
|
||
pad = width - reqd;
|
||
if (pad > 0) {
|
||
e += pad;
|
||
while (pad-- > 0) *p++ = ' ';
|
||
}
|
||
if (sign) *p++ = '-';
|
||
if (decpt <= 0 && prec > 0) {
|
||
*p++ = '0';
|
||
*p++ = '.';
|
||
e++;
|
||
while ( decpt < 0 ) {
|
||
decpt++;
|
||
*p++ = '0';
|
||
}
|
||
}
|
||
while (p < e) {
|
||
*p++ = *s++;
|
||
if (p == e) break;
|
||
if (--decpt == 0) *p++ = '.';
|
||
}
|
||
if (width < 0) {
|
||
pad = (reqd + width) * -1;
|
||
while (pad-- > 0) *p++ = ' ';
|
||
}
|
||
*p = 0;
|
||
return sout;
|
||
}
|
||
#endif
|
||
|
||
|
||
namespace xod {
|
||
//----------------------------------------------------------------------------
|
||
// Type definitions
|
||
//----------------------------------------------------------------------------
|
||
typedef double Number;
|
||
typedef bool Logic;
|
||
typedef unsigned long TimeMs;
|
||
typedef uint8_t DirtyFlags;
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Global variables
|
||
//----------------------------------------------------------------------------
|
||
|
||
TimeMs g_transactionTime;
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Metaprogramming utilities
|
||
//----------------------------------------------------------------------------
|
||
|
||
template<typename T> struct always_false {
|
||
enum { value = 0 };
|
||
};
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Forward declarations
|
||
//----------------------------------------------------------------------------
|
||
|
||
TimeMs transactionTime();
|
||
void runTransaction(bool firstRun);
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Engine (private API)
|
||
//----------------------------------------------------------------------------
|
||
|
||
namespace detail {
|
||
|
||
template<typename NodeT>
|
||
bool isTimedOut(const NodeT* node) {
|
||
TimeMs t = node->timeoutAt;
|
||
// TODO: deal with uint32 overflow
|
||
return t && t < transactionTime();
|
||
}
|
||
|
||
// Marks timed out node dirty. Do not reset timeoutAt here to give
|
||
// a chance for a node to get a reasonable result from `isTimedOut`
|
||
// later during its `evaluate`
|
||
template<typename NodeT>
|
||
void checkTriggerTimeout(NodeT* node) {
|
||
node->isNodeDirty |= isTimedOut(node);
|
||
}
|
||
|
||
template<typename NodeT>
|
||
void clearTimeout(NodeT* node) {
|
||
node->timeoutAt = 0;
|
||
}
|
||
|
||
template<typename NodeT>
|
||
void clearStaleTimeout(NodeT* node) {
|
||
if (isTimedOut(node))
|
||
clearTimeout(node);
|
||
}
|
||
|
||
} // namespace detail
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Public API (can be used by native nodes’ `evaluate` functions)
|
||
//----------------------------------------------------------------------------
|
||
|
||
TimeMs transactionTime() {
|
||
return g_transactionTime;
|
||
}
|
||
|
||
template<typename ContextT>
|
||
void setTimeout(ContextT* ctx, TimeMs timeout) {
|
||
ctx->_node->timeoutAt = transactionTime() + timeout;
|
||
}
|
||
|
||
template<typename ContextT>
|
||
void clearTimeout(ContextT* ctx) {
|
||
detail::clearTimeout(ctx->_node);
|
||
}
|
||
|
||
template<typename ContextT>
|
||
bool isTimedOut(const ContextT* ctx) {
|
||
return detail::isTimedOut(ctx->_node);
|
||
}
|
||
|
||
} // namespace xod
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Entry point
|
||
//----------------------------------------------------------------------------
|
||
void setup() {
|
||
// FIXME: looks like there is a rounding bug. Waiting for 100ms fights it
|
||
delay(100);
|
||
#ifdef XOD_DEBUG
|
||
DEBUG_SERIAL.begin(115200);
|
||
#endif
|
||
XOD_TRACE_FLN("\n\nProgram started");
|
||
|
||
xod::runTransaction(true);
|
||
}
|
||
|
||
void loop() {
|
||
xod::runTransaction(false);
|
||
}
|
||
|
||
/*=============================================================================
|
||
*
|
||
*
|
||
* Native node implementations
|
||
*
|
||
*
|
||
=============================================================================*/
|
||
|
||
namespace xod {
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// xod/core/gate implementation
|
||
//-----------------------------------------------------------------------------
|
||
namespace xod__core__gate {
|
||
|
||
struct State {
|
||
};
|
||
|
||
struct Node {
|
||
State state;
|
||
Logic output_T;
|
||
Logic output_F;
|
||
|
||
union {
|
||
struct {
|
||
bool isOutputDirty_T : 1;
|
||
bool isOutputDirty_F : 1;
|
||
bool isNodeDirty : 1;
|
||
};
|
||
|
||
DirtyFlags dirtyFlags;
|
||
};
|
||
};
|
||
|
||
struct input_GATE { };
|
||
struct input_TRIG { };
|
||
struct output_T { };
|
||
struct output_F { };
|
||
|
||
template<typename PinT> struct ValueType { using T = void; };
|
||
template<> struct ValueType<input_GATE> { using T = Logic; };
|
||
template<> struct ValueType<input_TRIG> { using T = Logic; };
|
||
template<> struct ValueType<output_T> { using T = Logic; };
|
||
template<> struct ValueType<output_F> { using T = Logic; };
|
||
|
||
struct ContextObject {
|
||
Node* _node;
|
||
|
||
Logic _input_GATE;
|
||
Logic _input_TRIG;
|
||
|
||
bool _isInputDirty_TRIG;
|
||
};
|
||
|
||
using Context = ContextObject*;
|
||
|
||
template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
|
||
static_assert(always_false<PinT>::value,
|
||
"Invalid pin descriptor. Expected one of:" \
|
||
" input_GATE input_TRIG" \
|
||
" output_T output_F");
|
||
}
|
||
|
||
template<> Logic getValue<input_GATE>(Context ctx) {
|
||
return ctx->_input_GATE;
|
||
}
|
||
template<> Logic getValue<input_TRIG>(Context ctx) {
|
||
return ctx->_input_TRIG;
|
||
}
|
||
template<> Logic getValue<output_T>(Context ctx) {
|
||
return ctx->_node->output_T;
|
||
}
|
||
template<> Logic getValue<output_F>(Context ctx) {
|
||
return ctx->_node->output_F;
|
||
}
|
||
|
||
template<typename InputT> bool isInputDirty(Context ctx) {
|
||
static_assert(always_false<InputT>::value,
|
||
"Invalid input descriptor. Expected one of:" \
|
||
" input_TRIG");
|
||
return false;
|
||
}
|
||
|
||
template<> bool isInputDirty<input_TRIG>(Context ctx) {
|
||
return ctx->_isInputDirty_TRIG;
|
||
}
|
||
|
||
template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
|
||
static_assert(always_false<OutputT>::value,
|
||
"Invalid output descriptor. Expected one of:" \
|
||
" output_T output_F");
|
||
}
|
||
|
||
template<> void emitValue<output_T>(Context ctx, Logic val) {
|
||
ctx->_node->output_T = val;
|
||
ctx->_node->isOutputDirty_T = true;
|
||
}
|
||
template<> void emitValue<output_F>(Context ctx, Logic val) {
|
||
ctx->_node->output_F = val;
|
||
ctx->_node->isOutputDirty_F = true;
|
||
}
|
||
|
||
State* getState(Context ctx) {
|
||
return &ctx->_node->state;
|
||
}
|
||
|
||
void evaluate(Context ctx) {
|
||
if (!isInputDirty<input_TRIG>(ctx))
|
||
return;
|
||
|
||
if (getValue<input_GATE>(ctx)) {
|
||
emitValue<output_T>(ctx, 1);
|
||
} else {
|
||
emitValue<output_F>(ctx, 1);
|
||
}
|
||
}
|
||
|
||
} // namespace xod__core__gate
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// xod/core/digital_input implementation
|
||
//-----------------------------------------------------------------------------
|
||
namespace xod__core__digital_input {
|
||
|
||
struct State {
|
||
int configuredPort = -1;
|
||
};
|
||
|
||
struct Node {
|
||
State state;
|
||
Logic output_SIG;
|
||
|
||
union {
|
||
struct {
|
||
bool isOutputDirty_SIG : 1;
|
||
bool isNodeDirty : 1;
|
||
};
|
||
|
||
DirtyFlags dirtyFlags;
|
||
};
|
||
};
|
||
|
||
struct input_PORT { };
|
||
struct input_UPD { };
|
||
struct output_SIG { };
|
||
|
||
template<typename PinT> struct ValueType { using T = void; };
|
||
template<> struct ValueType<input_PORT> { using T = Number; };
|
||
template<> struct ValueType<input_UPD> { using T = Logic; };
|
||
template<> struct ValueType<output_SIG> { using T = Logic; };
|
||
|
||
struct ContextObject {
|
||
Node* _node;
|
||
|
||
Number _input_PORT;
|
||
Logic _input_UPD;
|
||
|
||
bool _isInputDirty_UPD;
|
||
};
|
||
|
||
using Context = ContextObject*;
|
||
|
||
template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
|
||
static_assert(always_false<PinT>::value,
|
||
"Invalid pin descriptor. Expected one of:" \
|
||
" input_PORT input_UPD" \
|
||
" output_SIG");
|
||
}
|
||
|
||
template<> Number getValue<input_PORT>(Context ctx) {
|
||
return ctx->_input_PORT;
|
||
}
|
||
template<> Logic getValue<input_UPD>(Context ctx) {
|
||
return ctx->_input_UPD;
|
||
}
|
||
template<> Logic getValue<output_SIG>(Context ctx) {
|
||
return ctx->_node->output_SIG;
|
||
}
|
||
|
||
template<typename InputT> bool isInputDirty(Context ctx) {
|
||
static_assert(always_false<InputT>::value,
|
||
"Invalid input descriptor. Expected one of:" \
|
||
" input_UPD");
|
||
return false;
|
||
}
|
||
|
||
template<> bool isInputDirty<input_UPD>(Context ctx) {
|
||
return ctx->_isInputDirty_UPD;
|
||
}
|
||
|
||
template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
|
||
static_assert(always_false<OutputT>::value,
|
||
"Invalid output descriptor. Expected one of:" \
|
||
" output_SIG");
|
||
}
|
||
|
||
template<> void emitValue<output_SIG>(Context ctx, Logic val) {
|
||
ctx->_node->output_SIG = val;
|
||
ctx->_node->isOutputDirty_SIG = true;
|
||
}
|
||
|
||
State* getState(Context ctx) {
|
||
return &ctx->_node->state;
|
||
}
|
||
|
||
void evaluate(Context ctx) {
|
||
if (!isInputDirty<input_UPD>(ctx))
|
||
return;
|
||
|
||
State* state = getState(ctx);
|
||
const int port = (int)getValue<input_PORT>(ctx);
|
||
if (port != state->configuredPort) {
|
||
::pinMode(port, INPUT);
|
||
// Store configured port so to avoid repeating `pinMode` on
|
||
// subsequent requests
|
||
state->configuredPort = port;
|
||
}
|
||
|
||
emitValue<output_SIG>(ctx, ::digitalRead(port));
|
||
}
|
||
|
||
} // namespace xod__core__digital_input
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// xod/core/flip_flop implementation
|
||
//-----------------------------------------------------------------------------
|
||
namespace xod__core__flip_flop {
|
||
|
||
struct State {
|
||
};
|
||
|
||
struct Node {
|
||
State state;
|
||
Logic output_MEM;
|
||
|
||
union {
|
||
struct {
|
||
bool isOutputDirty_MEM : 1;
|
||
bool isNodeDirty : 1;
|
||
};
|
||
|
||
DirtyFlags dirtyFlags;
|
||
};
|
||
};
|
||
|
||
struct input_SET { };
|
||
struct input_TGL { };
|
||
struct input_RST { };
|
||
struct output_MEM { };
|
||
|
||
template<typename PinT> struct ValueType { using T = void; };
|
||
template<> struct ValueType<input_SET> { using T = Logic; };
|
||
template<> struct ValueType<input_TGL> { using T = Logic; };
|
||
template<> struct ValueType<input_RST> { using T = Logic; };
|
||
template<> struct ValueType<output_MEM> { using T = Logic; };
|
||
|
||
struct ContextObject {
|
||
Node* _node;
|
||
|
||
Logic _input_SET;
|
||
Logic _input_TGL;
|
||
Logic _input_RST;
|
||
|
||
bool _isInputDirty_SET;
|
||
bool _isInputDirty_TGL;
|
||
bool _isInputDirty_RST;
|
||
};
|
||
|
||
using Context = ContextObject*;
|
||
|
||
template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
|
||
static_assert(always_false<PinT>::value,
|
||
"Invalid pin descriptor. Expected one of:" \
|
||
" input_SET input_TGL input_RST" \
|
||
" output_MEM");
|
||
}
|
||
|
||
template<> Logic getValue<input_SET>(Context ctx) {
|
||
return ctx->_input_SET;
|
||
}
|
||
template<> Logic getValue<input_TGL>(Context ctx) {
|
||
return ctx->_input_TGL;
|
||
}
|
||
template<> Logic getValue<input_RST>(Context ctx) {
|
||
return ctx->_input_RST;
|
||
}
|
||
template<> Logic getValue<output_MEM>(Context ctx) {
|
||
return ctx->_node->output_MEM;
|
||
}
|
||
|
||
template<typename InputT> bool isInputDirty(Context ctx) {
|
||
static_assert(always_false<InputT>::value,
|
||
"Invalid input descriptor. Expected one of:" \
|
||
" input_SET input_TGL input_RST");
|
||
return false;
|
||
}
|
||
|
||
template<> bool isInputDirty<input_SET>(Context ctx) {
|
||
return ctx->_isInputDirty_SET;
|
||
}
|
||
template<> bool isInputDirty<input_TGL>(Context ctx) {
|
||
return ctx->_isInputDirty_TGL;
|
||
}
|
||
template<> bool isInputDirty<input_RST>(Context ctx) {
|
||
return ctx->_isInputDirty_RST;
|
||
}
|
||
|
||
template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
|
||
static_assert(always_false<OutputT>::value,
|
||
"Invalid output descriptor. Expected one of:" \
|
||
" output_MEM");
|
||
}
|
||
|
||
template<> void emitValue<output_MEM>(Context ctx, Logic val) {
|
||
ctx->_node->output_MEM = val;
|
||
ctx->_node->isOutputDirty_MEM = true;
|
||
}
|
||
|
||
State* getState(Context ctx) {
|
||
return &ctx->_node->state;
|
||
}
|
||
|
||
void evaluate(Context ctx) {
|
||
bool oldState = getValue<output_MEM>(ctx);
|
||
bool newState = oldState;
|
||
|
||
if (isInputDirty<input_TGL>(ctx)) {
|
||
newState = !oldState;
|
||
} else if (isInputDirty<input_SET>(ctx)) {
|
||
newState = true;
|
||
} else {
|
||
newState = false;
|
||
}
|
||
|
||
if (newState == oldState)
|
||
return;
|
||
|
||
emitValue<output_MEM>(ctx, newState);
|
||
}
|
||
|
||
} // namespace xod__core__flip_flop
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// xod/core/digital_output implementation
|
||
//-----------------------------------------------------------------------------
|
||
namespace xod__core__digital_output {
|
||
|
||
struct State {
|
||
int configuredPort = -1;
|
||
};
|
||
|
||
struct Node {
|
||
State state;
|
||
|
||
union {
|
||
struct {
|
||
bool isNodeDirty : 1;
|
||
};
|
||
|
||
DirtyFlags dirtyFlags;
|
||
};
|
||
};
|
||
|
||
struct input_PORT { };
|
||
struct input_SIG { };
|
||
|
||
template<typename PinT> struct ValueType { using T = void; };
|
||
template<> struct ValueType<input_PORT> { using T = Number; };
|
||
template<> struct ValueType<input_SIG> { using T = Logic; };
|
||
|
||
struct ContextObject {
|
||
Node* _node;
|
||
|
||
Number _input_PORT;
|
||
Logic _input_SIG;
|
||
|
||
};
|
||
|
||
using Context = ContextObject*;
|
||
|
||
template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
|
||
static_assert(always_false<PinT>::value,
|
||
"Invalid pin descriptor. Expected one of:" \
|
||
" input_PORT input_SIG" \
|
||
"");
|
||
}
|
||
|
||
template<> Number getValue<input_PORT>(Context ctx) {
|
||
return ctx->_input_PORT;
|
||
}
|
||
template<> Logic getValue<input_SIG>(Context ctx) {
|
||
return ctx->_input_SIG;
|
||
}
|
||
|
||
template<typename InputT> bool isInputDirty(Context ctx) {
|
||
static_assert(always_false<InputT>::value,
|
||
"Invalid input descriptor. Expected one of:" \
|
||
"");
|
||
return false;
|
||
}
|
||
|
||
template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
|
||
static_assert(always_false<OutputT>::value,
|
||
"Invalid output descriptor. Expected one of:" \
|
||
"");
|
||
}
|
||
|
||
State* getState(Context ctx) {
|
||
return &ctx->_node->state;
|
||
}
|
||
|
||
void evaluate(Context ctx) {
|
||
State* state = getState(ctx);
|
||
const int port = (int)getValue<input_PORT>(ctx);
|
||
if (port != state->configuredPort) {
|
||
::pinMode(port, OUTPUT);
|
||
// Store configured port so to avoid repeating `pinMode` call if just
|
||
// SIG is updated
|
||
state->configuredPort = port;
|
||
}
|
||
|
||
const bool val = getValue<input_SIG>(ctx);
|
||
::digitalWrite(port, val);
|
||
}
|
||
|
||
} // namespace xod__core__digital_output
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// xod/core/continuously implementation
|
||
//-----------------------------------------------------------------------------
|
||
namespace xod__core__continuously {
|
||
|
||
struct State {
|
||
};
|
||
|
||
struct Node {
|
||
State state;
|
||
TimeMs timeoutAt;
|
||
Logic output_TICK;
|
||
|
||
union {
|
||
struct {
|
||
bool isOutputDirty_TICK : 1;
|
||
bool isNodeDirty : 1;
|
||
};
|
||
|
||
DirtyFlags dirtyFlags;
|
||
};
|
||
};
|
||
|
||
struct output_TICK { };
|
||
|
||
template<typename PinT> struct ValueType { using T = void; };
|
||
template<> struct ValueType<output_TICK> { using T = Logic; };
|
||
|
||
struct ContextObject {
|
||
Node* _node;
|
||
|
||
};
|
||
|
||
using Context = ContextObject*;
|
||
|
||
template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
|
||
static_assert(always_false<PinT>::value,
|
||
"Invalid pin descriptor. Expected one of:" \
|
||
"" \
|
||
" output_TICK");
|
||
}
|
||
|
||
template<> Logic getValue<output_TICK>(Context ctx) {
|
||
return ctx->_node->output_TICK;
|
||
}
|
||
|
||
template<typename InputT> bool isInputDirty(Context ctx) {
|
||
static_assert(always_false<InputT>::value,
|
||
"Invalid input descriptor. Expected one of:" \
|
||
"");
|
||
return false;
|
||
}
|
||
|
||
template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
|
||
static_assert(always_false<OutputT>::value,
|
||
"Invalid output descriptor. Expected one of:" \
|
||
" output_TICK");
|
||
}
|
||
|
||
template<> void emitValue<output_TICK>(Context ctx, Logic val) {
|
||
ctx->_node->output_TICK = val;
|
||
ctx->_node->isOutputDirty_TICK = true;
|
||
}
|
||
|
||
State* getState(Context ctx) {
|
||
return &ctx->_node->state;
|
||
}
|
||
|
||
void evaluate(Context ctx) {
|
||
emitValue<output_TICK>(ctx, 1);
|
||
setTimeout(ctx, 0);
|
||
}
|
||
|
||
} // namespace xod__core__continuously
|
||
|
||
} // namespace xod
|
||
|
||
|
||
/*=============================================================================
|
||
*
|
||
*
|
||
* Main loop components
|
||
*
|
||
*
|
||
=============================================================================*/
|
||
|
||
namespace xod {
|
||
|
||
// Define/allocate persistent storages (state, timeout, output data) for all nodes
|
||
|
||
constexpr Number node_0_output_VAL = 12;
|
||
|
||
constexpr Number node_1_output_VAL = 11;
|
||
|
||
constexpr Number node_2_output_VAL = 13;
|
||
|
||
constexpr Logic node_3_output_TICK = false;
|
||
xod__core__continuously::Node node_3 = {
|
||
xod__core__continuously::State(), // state default
|
||
0, // timeoutAt
|
||
node_3_output_TICK, // output TICK default
|
||
false, // TICK dirty
|
||
true // node itself dirty
|
||
};
|
||
|
||
constexpr Logic node_4_output_SIG = false;
|
||
xod__core__digital_input::Node node_4 = {
|
||
xod__core__digital_input::State(), // state default
|
||
node_4_output_SIG, // output SIG default
|
||
true, // SIG dirty
|
||
true // node itself dirty
|
||
};
|
||
|
||
constexpr Logic node_5_output_SIG = false;
|
||
xod__core__digital_input::Node node_5 = {
|
||
xod__core__digital_input::State(), // state default
|
||
node_5_output_SIG, // output SIG default
|
||
true, // SIG dirty
|
||
true // node itself dirty
|
||
};
|
||
|
||
constexpr Logic node_6_output_T = false;
|
||
constexpr Logic node_6_output_F = false;
|
||
xod__core__gate::Node node_6 = {
|
||
xod__core__gate::State(), // state default
|
||
node_6_output_T, // output T default
|
||
node_6_output_F, // output F default
|
||
false, // T dirty
|
||
false, // F dirty
|
||
true // node itself dirty
|
||
};
|
||
|
||
constexpr Logic node_7_output_T = false;
|
||
constexpr Logic node_7_output_F = false;
|
||
xod__core__gate::Node node_7 = {
|
||
xod__core__gate::State(), // state default
|
||
node_7_output_T, // output T default
|
||
node_7_output_F, // output F default
|
||
false, // T dirty
|
||
false, // F dirty
|
||
true // node itself dirty
|
||
};
|
||
|
||
constexpr Logic node_8_output_MEM = false;
|
||
xod__core__flip_flop::Node node_8 = {
|
||
xod__core__flip_flop::State(), // state default
|
||
node_8_output_MEM, // output MEM default
|
||
true, // MEM dirty
|
||
true // node itself dirty
|
||
};
|
||
|
||
xod__core__digital_output::Node node_9 = {
|
||
xod__core__digital_output::State(), // state default
|
||
true // node itself dirty
|
||
};
|
||
|
||
void runTransaction(bool firstRun) {
|
||
g_transactionTime = millis();
|
||
|
||
XOD_TRACE_F("Transaction started, t=");
|
||
XOD_TRACE_LN(g_transactionTime);
|
||
|
||
// Check for timeouts
|
||
detail::checkTriggerTimeout(&node_3);
|
||
|
||
// defer-* nodes are always at the very bottom of the graph, so no one will
|
||
// recieve values emitted by them. We must evaluate them before everybody
|
||
// else to give them a chance to emit values.
|
||
//
|
||
// If trigerred, keep only output dirty, not the node itself, so it will
|
||
// evaluate on the regular pass only if it pushed a new value again.
|
||
|
||
// Evaluate all dirty nodes
|
||
{ // xod__core__continuously #3
|
||
if (node_3.isNodeDirty) {
|
||
XOD_TRACE_F("Eval node #");
|
||
XOD_TRACE_LN(3);
|
||
|
||
xod__core__continuously::ContextObject ctxObj;
|
||
ctxObj._node = &node_3;
|
||
|
||
// copy data from upstream nodes into context
|
||
|
||
xod__core__continuously::evaluate(&ctxObj);
|
||
|
||
// mark downstream nodes dirty
|
||
node_6.isNodeDirty |= node_3.isOutputDirty_TICK;
|
||
node_4.isNodeDirty |= node_3.isOutputDirty_TICK;
|
||
node_7.isNodeDirty |= node_3.isOutputDirty_TICK;
|
||
node_5.isNodeDirty |= node_3.isOutputDirty_TICK;
|
||
}
|
||
}
|
||
{ // xod__core__digital_input #4
|
||
if (node_4.isNodeDirty) {
|
||
XOD_TRACE_F("Eval node #");
|
||
XOD_TRACE_LN(4);
|
||
|
||
xod__core__digital_input::ContextObject ctxObj;
|
||
ctxObj._node = &node_4;
|
||
|
||
// copy data from upstream nodes into context
|
||
ctxObj._input_PORT = node_0_output_VAL;
|
||
ctxObj._input_UPD = node_3.output_TICK;
|
||
|
||
ctxObj._isInputDirty_UPD = node_3.isOutputDirty_TICK;
|
||
|
||
xod__core__digital_input::evaluate(&ctxObj);
|
||
|
||
// mark downstream nodes dirty
|
||
node_6.isNodeDirty |= node_4.isOutputDirty_SIG;
|
||
}
|
||
}
|
||
{ // xod__core__digital_input #5
|
||
if (node_5.isNodeDirty) {
|
||
XOD_TRACE_F("Eval node #");
|
||
XOD_TRACE_LN(5);
|
||
|
||
xod__core__digital_input::ContextObject ctxObj;
|
||
ctxObj._node = &node_5;
|
||
|
||
// copy data from upstream nodes into context
|
||
ctxObj._input_PORT = node_1_output_VAL;
|
||
ctxObj._input_UPD = node_3.output_TICK;
|
||
|
||
ctxObj._isInputDirty_UPD = node_3.isOutputDirty_TICK;
|
||
|
||
xod__core__digital_input::evaluate(&ctxObj);
|
||
|
||
// mark downstream nodes dirty
|
||
node_7.isNodeDirty |= node_5.isOutputDirty_SIG;
|
||
}
|
||
}
|
||
{ // xod__core__gate #6
|
||
if (node_6.isNodeDirty) {
|
||
XOD_TRACE_F("Eval node #");
|
||
XOD_TRACE_LN(6);
|
||
|
||
xod__core__gate::ContextObject ctxObj;
|
||
ctxObj._node = &node_6;
|
||
|
||
// copy data from upstream nodes into context
|
||
ctxObj._input_GATE = node_4.output_SIG;
|
||
ctxObj._input_TRIG = node_3.output_TICK;
|
||
|
||
ctxObj._isInputDirty_TRIG = node_3.isOutputDirty_TICK;
|
||
|
||
xod__core__gate::evaluate(&ctxObj);
|
||
|
||
// mark downstream nodes dirty
|
||
node_8.isNodeDirty |= node_6.isOutputDirty_F;
|
||
}
|
||
}
|
||
{ // xod__core__gate #7
|
||
if (node_7.isNodeDirty) {
|
||
XOD_TRACE_F("Eval node #");
|
||
XOD_TRACE_LN(7);
|
||
|
||
xod__core__gate::ContextObject ctxObj;
|
||
ctxObj._node = &node_7;
|
||
|
||
// copy data from upstream nodes into context
|
||
ctxObj._input_GATE = node_5.output_SIG;
|
||
ctxObj._input_TRIG = node_3.output_TICK;
|
||
|
||
ctxObj._isInputDirty_TRIG = node_3.isOutputDirty_TICK;
|
||
|
||
xod__core__gate::evaluate(&ctxObj);
|
||
|
||
// mark downstream nodes dirty
|
||
node_8.isNodeDirty |= node_7.isOutputDirty_F;
|
||
}
|
||
}
|
||
{ // xod__core__flip_flop #8
|
||
if (node_8.isNodeDirty) {
|
||
XOD_TRACE_F("Eval node #");
|
||
XOD_TRACE_LN(8);
|
||
|
||
xod__core__flip_flop::ContextObject ctxObj;
|
||
ctxObj._node = &node_8;
|
||
|
||
// copy data from upstream nodes into context
|
||
ctxObj._input_SET = node_7.output_F;
|
||
ctxObj._input_RST = node_6.output_F;
|
||
|
||
ctxObj._isInputDirty_TGL = false;
|
||
ctxObj._isInputDirty_SET = node_7.isOutputDirty_F;
|
||
ctxObj._isInputDirty_RST = node_6.isOutputDirty_F;
|
||
|
||
xod__core__flip_flop::evaluate(&ctxObj);
|
||
|
||
// mark downstream nodes dirty
|
||
node_9.isNodeDirty |= node_8.isOutputDirty_MEM;
|
||
}
|
||
}
|
||
{ // xod__core__digital_output #9
|
||
if (node_9.isNodeDirty) {
|
||
XOD_TRACE_F("Eval node #");
|
||
XOD_TRACE_LN(9);
|
||
|
||
xod__core__digital_output::ContextObject ctxObj;
|
||
ctxObj._node = &node_9;
|
||
|
||
// copy data from upstream nodes into context
|
||
ctxObj._input_PORT = node_2_output_VAL;
|
||
ctxObj._input_SIG = node_8.output_MEM;
|
||
|
||
xod__core__digital_output::evaluate(&ctxObj);
|
||
|
||
// mark downstream nodes dirty
|
||
}
|
||
}
|
||
|
||
// Clear dirtieness and timeouts for all nodes and pins
|
||
node_3.dirtyFlags = 0;
|
||
node_4.dirtyFlags = 0;
|
||
node_5.dirtyFlags = 0;
|
||
node_6.dirtyFlags = 0;
|
||
node_7.dirtyFlags = 0;
|
||
node_8.dirtyFlags = 0;
|
||
node_9.dirtyFlags = 0;
|
||
detail::clearStaleTimeout(&node_3);
|
||
|
||
XOD_TRACE_F("Transaction completed, t=");
|
||
XOD_TRACE_LN(millis());
|
||
}
|
||
|
||
} // namespace xod
|