Skip to content

Commit

Permalink
Replace invariant behavior with return codes
Browse files Browse the repository at this point in the history
  • Loading branch information
nick-thompson committed Sep 27, 2023
1 parent 57d9ce3 commit 5b84563
Show file tree
Hide file tree
Showing 20 changed files with 336 additions and 201 deletions.
11 changes: 6 additions & 5 deletions runtime/GraphNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ namespace elem
//
// Thread safety must be managed by the user. This method will be called on
// a non-realtime thread.
virtual void setProperty(std::string const& key, js::Value const& val);
virtual void setProperty(std::string const& key, js::Value const& val, SharedResourceMap<FloatType>& resources);
virtual int setProperty(std::string const& key, js::Value const& val);
virtual int setProperty(std::string const& key, js::Value const& val, SharedResourceMap<FloatType>& resources);

// Retreives a property from the Node's props, falling back to the provided
// default value if no property exists by the given name.
Expand Down Expand Up @@ -102,13 +102,14 @@ namespace elem
}

template <typename FloatType>
void GraphNode<FloatType>::setProperty(std::string const& key, js::Value const& val) {
int GraphNode<FloatType>::setProperty(std::string const& key, js::Value const& val) {
props.insert_or_assign(key, val);
return ReturnCode::Ok();
}

template <typename FloatType>
void GraphNode<FloatType>::setProperty(std::string const& key, js::Value const& val, SharedResourceMap<FloatType>&) {
setProperty(key, val);
int GraphNode<FloatType>::setProperty(std::string const& key, js::Value const& val, SharedResourceMap<FloatType>&) {
return setProperty(key, val);
}

template <typename FloatType>
Expand Down
23 changes: 0 additions & 23 deletions runtime/Invariant.h

This file was deleted.

123 changes: 84 additions & 39 deletions runtime/Runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include "DefaultNodeTypes.h"
#include "GraphNode.h"
#include "GraphRenderSequence.h"
#include "Invariant.h"
#include "Types.h"
#include "Value.h"
#include "JSON.h"
Expand Down Expand Up @@ -46,7 +45,7 @@ namespace elem

//==============================================================================
// Apply graph rendering instructions
void applyInstructions(js::Array const& batch);
int applyInstructions(js::Array const& batch);

// Run the internal audio processing callback
void process(
Expand Down Expand Up @@ -97,7 +96,7 @@ namespace elem
// instructions for your new type, such as those produced by the frontend
// from, e.g., `core.render(createNode("myNewNodeType", props, [children]))`
using NodeFactoryFn = std::function<std::shared_ptr<GraphNode<FloatType>>(NodeId const id, double sampleRate, int const blockSize)>;
void registerNodeType (std::string const& type, NodeFactoryFn && fn);
int registerNodeType (std::string const& type, NodeFactoryFn && fn);

private:
//==============================================================================
Expand All @@ -111,11 +110,11 @@ namespace elem
COMMIT_UPDATES = 5,
};

void createNode(int32_t const& nodeId, std::string const& type);
void deleteNode(int32_t const& nodeId);
void setProperty(int32_t const& nodeId, std::string const& prop, js::Value const& v);
void appendChild(int32_t const& parentId, int32_t const& childId);
void activateRoots(js::Array const& v);
int createNode(js::Value const& nodeId, js::Value const& type);
int deleteNode(js::Value const& nodeId);
int setProperty(js::Value const& nodeId, js::Value const& prop, js::Value const& v);
int appendChild(js::Value const& parentId, js::Value const& childId);
int activateRoots(js::Array const& v);

BufferAllocator<FloatType> bufferAllocator;
std::shared_ptr<GraphRenderSequence<FloatType>> rtRenderSeq;
Expand Down Expand Up @@ -154,39 +153,39 @@ namespace elem

//==============================================================================
template <typename FloatType>
void Runtime<FloatType>::applyInstructions(elem::js::Array const& batch)
int Runtime<FloatType>::applyInstructions(elem::js::Array const& batch)
{
bool shouldRebuild = false;

// TODO: For correct transaction semantics here, we should createNode into a separate
// map that only gets merged into the actual nodeMap on commitUpdaes
for (auto& next : batch) {
invariant(next.isArray(), "Expected a command array.");
if (!next.isArray())
return ReturnCode::InvalidInstructionFormat();

auto const& ar = next.getArray();

invariant(ar[0].isNumber(), "Expected a number type command.");
auto const cmd = static_cast<InstructionType>(static_cast<int>((elem::js::Number) ar[0]));
if(!ar[0].isNumber())
return ReturnCode::InvalidInstructionFormat();

auto varToInt = [](elem::js::Value const& v) -> int32_t {
invariant(v.isNumber(), "Expected a number type node identifier. Make sure you are using @elemaudio/[email protected]+");
return static_cast<int32_t>((elem::js::Number) v);
};
auto const cmd = static_cast<InstructionType>(static_cast<int>((elem::js::Number) ar[0]));
auto res = ReturnCode::Ok();

switch (cmd) {
case InstructionType::CREATE_NODE:
createNode(varToInt(ar[1]), (elem::js::String) ar[2]);
res = createNode(ar[1], ar[2]);
break;
case InstructionType::DELETE_NODE:
deleteNode(varToInt(ar[1]));
res = deleteNode(ar[1]);
break;
case InstructionType::SET_PROPERTY:
setProperty(varToInt(ar[1]), (elem::js::String) ar[2], ar[3]);
res = setProperty(ar[1], ar[2], ar[3]);
break;
case InstructionType::APPEND_CHILD:
appendChild(varToInt(ar[1]), varToInt(ar[2]));
res = appendChild(ar[1], ar[2]);
break;
case InstructionType::ACTIVATE_ROOTS:
activateRoots(ar[1].getArray());
res = activateRoots(ar[1]);
shouldRebuild = true;
break;
case InstructionType::COMMIT_UPDATES:
Expand All @@ -197,6 +196,11 @@ namespace elem
default:
break;
}

// TODO: And here we should abort the transaction and revert any applied properties
if (res != ReturnCode::Ok()) {
return res;
}
}

// While we're here, we scan the garbageTable to see if we can deallocate
Expand All @@ -210,6 +214,8 @@ namespace elem
it++;
}
}

return ReturnCode::Ok();
}

template <typename FloatType>
Expand All @@ -232,69 +238,104 @@ namespace elem

//==============================================================================
template <typename FloatType>
void Runtime<FloatType>::createNode(int32_t const& nodeId, std::string const& type)
int Runtime<FloatType>::createNode(js::Value const& a1, js::Value const& a2)
{
if (!a1.isNumber() || !a2.isString())
return ReturnCode::InvalidInstructionFormat();

auto const nodeId = static_cast<int32_t>((js::Number) a1);
auto const type = (js::String) a2;

ELEM_DBG("[Native] createNode " << type << "#" << nodeIdToHex(nodeId));

invariant(nodeFactory.find(type) != nodeFactory.end(), "Unknown node type " + type);
invariant(nodeTable.find(nodeId) == nodeTable.end(), "Trying to create a node which already exists.");
invariant(edgeTable.find(nodeId) == edgeTable.end(), "Trying to create a node which already exists.");
if (nodeFactory.find(type) == nodeFactory.end())
return ReturnCode::UnknownNodeType();
if (nodeTable.find(nodeId) != nodeTable.end() || edgeTable.find(nodeId) != edgeTable.end())
return ReturnCode::NodeAlreadyExists();

auto node = nodeFactory[type](nodeId, sampleRate, blockSize);
nodeTable.insert({nodeId, node});
edgeTable.insert({nodeId, {}});

return ReturnCode::Ok();
}

template <typename FloatType>
void Runtime<FloatType>::deleteNode(int32_t const& nodeId)
int Runtime<FloatType>::deleteNode(js::Value const& a1)
{
if (!a1.isNumber())
return ReturnCode::InvalidInstructionFormat();

auto const nodeId = static_cast<int32_t>((js::Number) a1);
ELEM_DBG("[Native] deleteNode " << nodeIdToHex(nodeId));

invariant(nodeTable.find(nodeId) != nodeTable.end(), "Trying to delete an unrecognized node.");
invariant(edgeTable.find(nodeId) != edgeTable.end(), "Trying to delete an unrecognized node.");
if (nodeTable.find(nodeId) == nodeTable.end() || edgeTable.find(nodeId) == edgeTable.end())
return ReturnCode::NodeNotFound();

// Move the node out of the nodeTable. It will be pruned from the garbageTable
// asynchronously after the renderer has dropped its references.
garbageTable.insert(nodeTable.extract(nodeId));
edgeTable.erase(nodeId);

return ReturnCode::Ok();
}

template <typename FloatType>
void Runtime<FloatType>::setProperty(int32_t const& nodeId, std::string const& prop, js::Value const& v)
int Runtime<FloatType>::setProperty(js::Value const& a1, js::Value const& a2, js::Value const& v)
{
if (!a1.isNumber() || !a2.isString())
return ReturnCode::InvalidInstructionFormat();

auto const nodeId = static_cast<int32_t>((js::Number) a1);
auto const prop = (js::String) a2;

ELEM_DBG("[Native] setProperty " << nodeIdToHex(nodeId) << " " << prop << " " << v.toString());

invariant(nodeTable.find(nodeId) != nodeTable.end(), "Trying to set a property for an unrecognized node.");
if (nodeTable.find(nodeId) == nodeTable.end())
return ReturnCode::NodeNotFound();

// This is intentionally called on the non-realtime thread. It is the job
// of the GraphNode to ensure thread safety between calls to setProperty
// and calls to its own proces function
nodeTable.at(nodeId)->setProperty(prop, v, sharedResourceMap);
return nodeTable.at(nodeId)->setProperty(prop, v, sharedResourceMap);
}

template <typename FloatType>
void Runtime<FloatType>::appendChild(int32_t const& parentId, int32_t const& childId)
int Runtime<FloatType>::appendChild(js::Value const& a1, js::Value const& a2)
{
if (!a1.isNumber() || !a2.isNumber())
return ReturnCode::InvalidInstructionFormat();

auto const parentId = static_cast<int32_t>((js::Number) a1);
auto const childId = static_cast<int32_t>((js::Number) a2);

ELEM_DBG("[Native] appendChild " << nodeIdToHex(childId) << " to parent " << nodeIdToHex(parentId));

invariant(nodeTable.find(parentId) != nodeTable.end(), "Trying to append a child to an unknown parent.");
invariant(edgeTable.find(parentId) != edgeTable.end(), "Trying to append a child to an unknown parent.");
invariant(nodeTable.find(childId) != nodeTable.end(), "Trying to append an unknown child to a parent.");
if (nodeTable.find(parentId) == nodeTable.end() || edgeTable.find(parentId) == edgeTable.end())
return ReturnCode::NodeNotFound();
if (nodeTable.find(childId) == nodeTable.end())
return ReturnCode::NodeNotFound();

edgeTable.at(parentId).push_back(childId);

return ReturnCode::Ok();
}

template <typename FloatType>
void Runtime<FloatType>::activateRoots(js::Array const& roots)
int Runtime<FloatType>::activateRoots(js::Array const& roots)
{
// Populate and activate from the incoming event
std::set<NodeId> active;

for (auto const& v : roots) {
if (!v.isNumber())
return ReturnCode::InvalidInstructionFormat();

int32_t nodeId = static_cast<int32_t>((js::Number) v);
ELEM_DBG("[Native] activateRoot " << nodeIdToHex(nodeId));

invariant(nodeTable.find(nodeId) != nodeTable.end(), "Trying to activate an unrecognized root node.");
if (nodeTable.find(nodeId) == nodeTable.end())
return ReturnCode::NodeNotFound();

if (auto ptr = std::dynamic_pointer_cast<RootNode<FloatType>>(nodeTable.at(nodeId))) {
ptr->setProperty("active", true);
Expand All @@ -319,6 +360,7 @@ namespace elem

// Merge
currentRoots = active;
return ReturnCode::Ok();
}

//==============================================================================
Expand Down Expand Up @@ -368,10 +410,13 @@ namespace elem
}

template <typename FloatType>
void Runtime<FloatType>::registerNodeType(std::string const& type, Runtime::NodeFactoryFn && fn)
int Runtime<FloatType>::registerNodeType(std::string const& type, Runtime::NodeFactoryFn && fn)
{
invariant(nodeFactory.find(type) == nodeFactory.end(), "Trying to install a node type which already exists");
if (nodeFactory.find(type) != nodeFactory.end())
return ReturnCode::NodeTypeAlreadyExists();

nodeFactory.emplace(type, std::move(fn));
return ReturnCode::Ok();
}

//==============================================================================
Expand Down
40 changes: 40 additions & 0 deletions runtime/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,46 @@ namespace elem
return "0x" + s;
}

//==============================================================================
// A static helper for enumerating and describing return codes used throughout
// the codebase
struct ReturnCode {
static int Ok() { return 0; }
static int UnknownNodeType() { return 1; }
static int NodeNotFound() { return 2; }
static int NodeAlreadyExists() { return 3; }
static int NodeTypeAlreadyExists() { return 4; }
static int InvalidPropertyType() { return 5; }
static int InvalidPropertyValue() { return 6; }
static int InvariantViolation() { return 7; }
static int InvalidInstructionFormat() { return 8; }

static std::string describe (int c) {
switch (c) {
case 0:
return "Ok";
case 1:
return "Node type not recognized";
case 2:
return "Node not found";
case 3:
return "Attempting to create a node that already exists";
case 4:
return "Attempting to create a node type that already exists";
case 5:
return "Invalid value type for the given node property";
case 6:
return "Invalid value for the given node property";
case 7:
return "Invariant violation";
case 8:
return "Invalid instruction format";
default:
return "Return code not recognized";
}
}
};

//==============================================================================
// A simple struct representing the inputs to a given GraphNode during the realtime
// audio block processing step.
Expand Down
Loading

0 comments on commit 5b84563

Please sign in to comment.