You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
525 lines
19 KiB
525 lines
19 KiB
/* |
|
* EEZ Modular Firmware |
|
* Copyright (C) 2021-present, Envox d.o.o. |
|
* |
|
* This program is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation, either version 3 of the License, or |
|
* (at your option) any later version. |
|
|
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
|
|
* You should have received a copy of the GNU General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
#include <stdio.h> |
|
|
|
#include <eez/core/debug.h> |
|
|
|
#include <eez/gui/gui.h> |
|
|
|
#include <eez/flow/flow.h> |
|
#include <eez/flow/operations.h> |
|
#include <eez/flow/queue.h> |
|
#include <eez/flow/debugger.h> |
|
#include <eez/flow/flow_defs_v3.h> |
|
#include <eez/flow/hooks.h> |
|
#include <eez/flow/components/call_action.h> |
|
|
|
using namespace eez::gui; |
|
|
|
namespace eez { |
|
namespace flow { |
|
|
|
bool isComponentReadyToRun(FlowState *flowState, unsigned componentIndex) { |
|
auto component = flowState->flow->components[componentIndex]; |
|
|
|
if (component->type == defs_v3::COMPONENT_TYPE_CATCH_ERROR_ACTION) { |
|
return false; |
|
} |
|
|
|
if (component->type == defs_v3::COMPONENT_TYPE_ON_EVENT_ACTION) { |
|
return false; |
|
} |
|
|
|
if (component->type < defs_v3::COMPONENT_TYPE_START_ACTION) { |
|
// always execute widget |
|
return true; |
|
} |
|
|
|
if (component->type == defs_v3::COMPONENT_TYPE_START_ACTION) { |
|
auto parentComponent = flowState->parentComponent; |
|
if (parentComponent) { |
|
auto flowInputIndex = parentComponent->inputs[0]; |
|
auto value = flowState->parentFlowState->values[flowInputIndex]; |
|
return value.getType() != VALUE_TYPE_UNDEFINED; |
|
} else { |
|
return true; |
|
} |
|
} |
|
|
|
// check if required inputs are defined: |
|
// - at least 1 seq input must be defined |
|
// - all non optional data inputs must be defined |
|
int numSeqInputs = 0; |
|
int numDefinedSeqInputs = 0; |
|
for (unsigned inputIndex = 0; inputIndex < component->inputs.count; inputIndex++) { |
|
auto inputValueIndex = component->inputs[inputIndex]; |
|
|
|
auto input = flowState->flow->componentInputs[inputValueIndex]; |
|
|
|
if (input & COMPONENT_INPUT_FLAG_IS_SEQ_INPUT) { |
|
numSeqInputs++; |
|
auto &value = flowState->values[inputValueIndex]; |
|
if (value.type != VALUE_TYPE_UNDEFINED) { |
|
numDefinedSeqInputs++; |
|
} |
|
} else { |
|
if (!(input & COMPONENT_INPUT_FLAG_IS_OPTIONAL)) { |
|
auto &value = flowState->values[inputValueIndex]; |
|
if (value.type == VALUE_TYPE_UNDEFINED) { |
|
// non optional data input is undefined |
|
return false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (numSeqInputs && !numDefinedSeqInputs) { |
|
// no seq input is defined |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static bool pingComponent(FlowState *flowState, unsigned componentIndex, int sourceComponentIndex = -1, int sourceOutputIndex = -1, int targetInputIndex = -1) { |
|
if (isComponentReadyToRun(flowState, componentIndex)) { |
|
if (!addToQueue(flowState, componentIndex, sourceComponentIndex, sourceOutputIndex, targetInputIndex)) { |
|
throwError(flowState, componentIndex, "Execution queue is full\n"); |
|
return false; |
|
} |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
static FlowState *initFlowState(Assets *assets, int flowIndex, FlowState *parentFlowState, int parentComponentIndex) { |
|
auto flowDefinition = static_cast<FlowDefinition *>(assets->flowDefinition); |
|
auto flow = flowDefinition->flows[flowIndex]; |
|
|
|
auto nValues = flow->componentInputs.count + flow->localVariables.count; |
|
|
|
FlowState *flowState = (FlowState *)alloc( |
|
sizeof(FlowState) + |
|
nValues * sizeof(Value) + |
|
flow->components.count * sizeof(ComponenentExecutionState *) + |
|
flow->components.count * sizeof(bool), |
|
0x4c3b6ef5 |
|
); |
|
|
|
flowState->flowStateIndex = (int)((uint8_t *)flowState - ALLOC_BUFFER); |
|
flowState->assets = assets; |
|
flowState->flowDefinition = static_cast<FlowDefinition *>(assets->flowDefinition); |
|
flowState->flow = flowDefinition->flows[flowIndex]; |
|
flowState->flowIndex = flowIndex; |
|
flowState->error = false; |
|
flowState->numAsyncComponents = 0; |
|
flowState->parentFlowState = parentFlowState; |
|
|
|
flowState->timelinePosition = 0; |
|
|
|
if (parentFlowState) { |
|
if (parentFlowState->lastChild) { |
|
parentFlowState->lastChild->nextSibling = flowState; |
|
flowState->previousSibling = parentFlowState->lastChild; |
|
parentFlowState->lastChild = flowState; |
|
} else { |
|
flowState->previousSibling = nullptr; |
|
parentFlowState->firstChild = flowState; |
|
parentFlowState->lastChild = flowState; |
|
} |
|
|
|
flowState->parentComponentIndex = parentComponentIndex; |
|
flowState->parentComponent = parentFlowState->flow->components[parentComponentIndex]; |
|
} else { |
|
if (g_lastFlowState) { |
|
g_lastFlowState->nextSibling = flowState; |
|
flowState->previousSibling = g_lastFlowState; |
|
g_lastFlowState = flowState; |
|
} else { |
|
flowState->previousSibling = nullptr; |
|
g_firstFlowState = flowState; |
|
g_lastFlowState = flowState; |
|
} |
|
|
|
flowState->parentComponentIndex = -1; |
|
flowState->parentComponent = nullptr; |
|
} |
|
|
|
flowState->firstChild = nullptr; |
|
flowState->lastChild = nullptr; |
|
flowState->nextSibling = nullptr; |
|
|
|
flowState->values = (Value *)(flowState + 1); |
|
flowState->componenentExecutionStates = (ComponenentExecutionState **)(flowState->values + nValues); |
|
flowState->componenentAsyncStates = (bool *)(flowState->componenentExecutionStates + flow->components.count); |
|
|
|
for (unsigned i = 0; i < nValues; i++) { |
|
new (flowState->values + i) Value(); |
|
} |
|
|
|
auto &undefinedValue = *flowDefinition->constants[UNDEFINED_VALUE_INDEX]; |
|
for (unsigned i = 0; i < flow->componentInputs.count; i++) { |
|
flowState->values[i] = undefinedValue; |
|
} |
|
|
|
for (unsigned i = 0; i < flow->localVariables.count; i++) { |
|
auto value = flow->localVariables[i]; |
|
flowState->values[flow->componentInputs.count + i] = *value; |
|
} |
|
|
|
for (unsigned i = 0; i < flow->components.count; i++) { |
|
flowState->componenentExecutionStates[i] = nullptr; |
|
flowState->componenentAsyncStates[i] = false; |
|
} |
|
|
|
onFlowStateCreated(flowState); |
|
|
|
for (unsigned componentIndex = 0; componentIndex < flow->components.count; componentIndex++) { |
|
pingComponent(flowState, componentIndex); |
|
} |
|
|
|
return flowState; |
|
} |
|
|
|
FlowState *initActionFlowState(int flowIndex, FlowState *parentFlowState, int parentComponentIndex) { |
|
auto flowState = initFlowState(parentFlowState->assets, flowIndex, parentFlowState, parentComponentIndex); |
|
if (flowState) { |
|
flowState->isAction = true; |
|
} |
|
return flowState; |
|
} |
|
|
|
FlowState *initPageFlowState(Assets *assets, int flowIndex, FlowState *parentFlowState, int parentComponentIndex) { |
|
auto flowState = initFlowState(assets, flowIndex, parentFlowState, parentComponentIndex); |
|
if (flowState) { |
|
flowState->isAction = false; |
|
} |
|
return flowState; |
|
} |
|
|
|
bool canFreeFlowState(FlowState *flowState, bool includingWatchVariable) { |
|
if (!flowState->isAction) { |
|
return false; |
|
} |
|
|
|
if (flowState->numAsyncComponents > 0) { |
|
return false; |
|
} |
|
|
|
if (isThereAnyTaskInQueueForFlowState(flowState, includingWatchVariable)) { |
|
return false; |
|
} |
|
|
|
for (uint32_t componentIndex = 0; componentIndex < flowState->flow->components.count; componentIndex++) { |
|
auto component = flowState->flow->components[componentIndex]; |
|
if ( |
|
component->type != defs_v3::COMPONENT_TYPE_INPUT_ACTION && |
|
component->type != defs_v3::COMPONENT_TYPE_LOOP_ACTION && |
|
component->type != defs_v3::COMPONENT_TYPE_COUNTER_ACTION && |
|
(includingWatchVariable || component->type != defs_v3::COMPONENT_TYPE_WATCH_VARIABLE_ACTION) && |
|
flowState->componenentExecutionStates[componentIndex] |
|
) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void freeFlowState(FlowState *flowState) { |
|
auto parentFlowState = flowState->parentFlowState; |
|
if (flowState->parentFlowState) { |
|
auto componentExecutionState = flowState->parentFlowState->componenentExecutionStates[flowState->parentComponentIndex]; |
|
if (componentExecutionState) { |
|
deallocateComponentExecutionState(flowState->parentFlowState, flowState->parentComponentIndex); |
|
return; |
|
} |
|
|
|
if (parentFlowState->firstChild == flowState) { |
|
parentFlowState->firstChild = flowState->nextSibling; |
|
} |
|
if (parentFlowState->lastChild == flowState) { |
|
parentFlowState->lastChild = flowState->previousSibling; |
|
} |
|
} else { |
|
if (g_firstFlowState == flowState) { |
|
g_firstFlowState = flowState->nextSibling; |
|
} |
|
if (g_lastFlowState == flowState) { |
|
g_lastFlowState = flowState->previousSibling; |
|
} |
|
} |
|
|
|
if (flowState->previousSibling) { |
|
flowState->previousSibling->nextSibling = flowState->nextSibling; |
|
} |
|
if (flowState->nextSibling) { |
|
flowState->nextSibling->previousSibling = flowState->previousSibling; |
|
} |
|
|
|
auto flow = flowState->flow; |
|
|
|
auto valuesCount = flow->componentInputs.count + flow->localVariables.count; |
|
|
|
for (unsigned int i = 0; i < valuesCount; i++) { |
|
(flowState->values + i)->~Value(); |
|
} |
|
|
|
for (unsigned i = 0; i < flow->components.count; i++) { |
|
deallocateComponentExecutionState(flowState, i); |
|
} |
|
|
|
onFlowStateDestroyed(flowState); |
|
|
|
free(flowState); |
|
|
|
if (parentFlowState) { |
|
if (canFreeFlowState(parentFlowState)) { |
|
freeFlowState(parentFlowState); |
|
} |
|
} |
|
} |
|
|
|
void deallocateComponentExecutionState(FlowState *flowState, unsigned componentIndex) { |
|
auto executionState = flowState->componenentExecutionStates[componentIndex]; |
|
if (executionState) { |
|
flowState->componenentExecutionStates[componentIndex] = nullptr; |
|
onComponentExecutionStateChanged(flowState, componentIndex); |
|
ObjectAllocator<ComponenentExecutionState>::deallocate(executionState); |
|
} |
|
} |
|
|
|
void propagateValue(FlowState *flowState, unsigned componentIndex, unsigned outputIndex, const gui::Value &value) { |
|
auto component = flowState->flow->components[componentIndex]; |
|
auto componentOutput = component->outputs[outputIndex]; |
|
|
|
auto value2 = value.getValue(); |
|
|
|
for (unsigned connectionIndex = 0; connectionIndex < componentOutput->connections.count; connectionIndex++) { |
|
auto connection = componentOutput->connections[connectionIndex]; |
|
|
|
auto pValue = &flowState->values[connection->targetInputIndex]; |
|
|
|
if (*pValue != value2) { |
|
*pValue = value2; |
|
|
|
//if (!(flowState->flow->componentInputs[connection->targetInputIndex] & COMPONENT_INPUT_FLAG_IS_SEQ_INPUT)) { |
|
onValueChanged(pValue); |
|
//} |
|
} |
|
|
|
pingComponent(flowState, connection->targetComponentIndex, componentIndex, outputIndex, connection->targetInputIndex); |
|
} |
|
} |
|
|
|
void propagateValue(FlowState *flowState, unsigned componentIndex, unsigned outputIndex) { |
|
auto &nullValue = *flowState->flowDefinition->constants[NULL_VALUE_INDEX]; |
|
propagateValue(flowState, componentIndex, outputIndex, nullValue); |
|
} |
|
|
|
void propagateValueThroughSeqout(FlowState *flowState, unsigned componentIndex) { |
|
// find @seqout output |
|
// TODO optimization hint: always place @seqout at 0-th index |
|
auto component = flowState->flow->components[componentIndex]; |
|
for (uint32_t i = 0; i < component->outputs.count; i++) { |
|
if (component->outputs[i]->isSeqOut) { |
|
propagateValue(flowState, componentIndex, i); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
void getValue(uint16_t dataId, DataOperationEnum operation, const WidgetCursor &widgetCursor, Value &value) { |
|
if (isFlowRunningHook()) { |
|
FlowState *flowState = widgetCursor.flowState; |
|
auto flow = flowState->flow; |
|
|
|
WidgetDataItem *widgetDataItem = flow->widgetDataItems[dataId]; |
|
if (widgetDataItem && widgetDataItem->componentIndex != -1 && widgetDataItem->propertyValueIndex != -1) { |
|
evalProperty(flowState, widgetDataItem->componentIndex, widgetDataItem->propertyValueIndex, value, "doGetFlowValue failed", nullptr, widgetCursor.iterators, operation); |
|
} |
|
} |
|
} |
|
|
|
void setValue(uint16_t dataId, const WidgetCursor &widgetCursor, const Value& value) { |
|
if (isFlowRunningHook()) { |
|
FlowState *flowState = widgetCursor.flowState; |
|
auto flow = flowState->flow; |
|
|
|
WidgetDataItem *widgetDataItem = flow->widgetDataItems[dataId]; |
|
if (widgetDataItem && widgetDataItem->componentIndex != -1 && widgetDataItem->propertyValueIndex != -1) { |
|
auto component = flow->components[widgetDataItem->componentIndex]; |
|
auto property = component->properties[widgetDataItem->propertyValueIndex]; |
|
Value dstValue; |
|
if (evalAssignableExpression(flowState, widgetDataItem->componentIndex, property->evalInstructions, dstValue, "doSetFlowValue failed", nullptr, widgetCursor.iterators)) { |
|
assignValue(flowState, widgetDataItem->componentIndex, dstValue, value); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
void assignValue(FlowState *flowState, int componentIndex, Value &dstValue, const Value &srcValue) { |
|
if (dstValue.getType() == VALUE_TYPE_FLOW_OUTPUT) { |
|
propagateValue(flowState, componentIndex, dstValue.getUInt16(), srcValue); |
|
} else if (dstValue.getType() == VALUE_TYPE_NATIVE_VARIABLE) { |
|
set(g_widgetCursor, dstValue.getInt(), srcValue); |
|
} else { |
|
Value *pDstValue; |
|
if (dstValue.getType() == VALUE_TYPE_ARRAY_ELEMENT_VALUE) { |
|
auto arrayElementValue = (ArrayElementValue *)dstValue.refValue; |
|
auto array = arrayElementValue->arrayValue.getArray(); |
|
pDstValue = &array->values[arrayElementValue->elementIndex]; |
|
} else { |
|
pDstValue = dstValue.pValueValue; |
|
} |
|
|
|
if (assignValue(*pDstValue, srcValue)) { |
|
onValueChanged(pDstValue); |
|
} else { |
|
char errorMessage[100]; |
|
snprintf(errorMessage, sizeof(errorMessage), "Can not assign %s to %s\n", |
|
g_valueTypeNames[pDstValue->type](srcValue), g_valueTypeNames[srcValue.type](*pDstValue) |
|
); |
|
throwError(flowState, componentIndex, errorMessage); |
|
} |
|
} |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
void startAsyncExecution(FlowState *flowState, int componentIndex) { |
|
if (!flowState->componenentAsyncStates[componentIndex]) { |
|
flowState->componenentAsyncStates[componentIndex] = true; |
|
onComponentAsyncStateChanged(flowState, componentIndex); |
|
|
|
flowState->numAsyncComponents++; |
|
} |
|
} |
|
|
|
void endAsyncExecution(FlowState *flowState, int componentIndex) { |
|
if (!g_firstFlowState) { |
|
return; |
|
} |
|
|
|
if (flowState->componenentAsyncStates[componentIndex]) { |
|
flowState->componenentAsyncStates[componentIndex] = false; |
|
onComponentAsyncStateChanged(flowState, componentIndex); |
|
|
|
flowState->numAsyncComponents--; |
|
|
|
if (canFreeFlowState(flowState)) { |
|
freeFlowState(flowState); |
|
} |
|
} |
|
} |
|
|
|
void onEvent(FlowState *flowState, FlowEvent flowEvent) { |
|
for (unsigned componentIndex = 0; componentIndex < flowState->flow->components.count; componentIndex++) { |
|
auto component = flowState->flow->components[componentIndex]; |
|
if (component->type == defs_v3::COMPONENT_TYPE_ON_EVENT_ACTION) { |
|
struct OnEventComponent : public Component { |
|
uint8_t event; |
|
}; |
|
auto onEventComponent = (OnEventComponent *)component; |
|
if (onEventComponent->event == flowEvent) { |
|
if (!addToQueue(flowState, componentIndex, -1, -1, -1, false)) { |
|
throwError(flowState, componentIndex, "Execution queue is full\n"); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
|
|
bool findCatchErrorComponent(FlowState *flowState, FlowState *&catchErrorFlowState, int &catchErrorComponentIndex) { |
|
if (!flowState) { |
|
return false; |
|
} |
|
|
|
for (unsigned componentIndex = 0; componentIndex < flowState->flow->components.count; componentIndex++) { |
|
auto component = flowState->flow->components[componentIndex]; |
|
if (component->type == defs_v3::COMPONENT_TYPE_CATCH_ERROR_ACTION) { |
|
catchErrorFlowState = flowState; |
|
catchErrorComponentIndex = componentIndex; |
|
return true; |
|
} |
|
} |
|
|
|
return findCatchErrorComponent(flowState->parentFlowState, catchErrorFlowState, catchErrorComponentIndex); |
|
} |
|
|
|
void throwError(FlowState *flowState, int componentIndex, const char *errorMessage) { |
|
auto component = flowState->flow->components[componentIndex]; |
|
|
|
#if defined(__EMSCRIPTEN__) |
|
printf("throwError: %s\n", errorMessage); |
|
#endif |
|
|
|
if (component->errorCatchOutput != -1) { |
|
propagateValue( |
|
flowState, |
|
componentIndex, |
|
component->errorCatchOutput, |
|
Value::makeStringRef(errorMessage, strlen(errorMessage), 0xef6f8414) |
|
); |
|
} else { |
|
FlowState *catchErrorFlowState; |
|
int catchErrorComponentIndex; |
|
if ( |
|
findCatchErrorComponent( |
|
component->type == defs_v3::COMPONENT_TYPE_ERROR_ACTION ? flowState->parentFlowState : flowState, |
|
catchErrorFlowState, |
|
catchErrorComponentIndex |
|
) |
|
) { |
|
auto catchErrorComponentExecutionState = allocateComponentExecutionState<CatchErrorComponenentExecutionState>(catchErrorFlowState, catchErrorComponentIndex); |
|
catchErrorComponentExecutionState->message = Value::makeStringRef(errorMessage, strlen(errorMessage), 0x9473eef2); |
|
|
|
if (!addToQueue(catchErrorFlowState, catchErrorComponentIndex, -1, -1, -1)) { |
|
catchErrorFlowState->error = true; |
|
onFlowError(catchErrorFlowState, catchErrorComponentIndex, "Execution queue is full\n"); |
|
stopScriptHook(); |
|
} |
|
} else { |
|
flowState->error = true; |
|
onFlowError(flowState, componentIndex, errorMessage); |
|
stopScriptHook(); |
|
} |
|
} |
|
} |
|
|
|
void throwError(FlowState *flowState, int componentIndex, const char *errorMessage, const char *errorMessageDescription) { |
|
if (errorMessage) { |
|
char throwErrorMessage[512]; |
|
snprintf(throwErrorMessage, sizeof(throwErrorMessage), "%s: %s", errorMessage, errorMessageDescription); |
|
throwError(flowState, componentIndex, throwErrorMessage); |
|
} else { |
|
throwError(flowState, componentIndex, errorMessageDescription); |
|
} |
|
} |
|
|
|
} // namespace flow |
|
} // namespace eez
|
|
|