diff --git a/art/hat-only-mini.png b/art/hat-only-mini.png new file mode 100644 index 0000000..6a3322e Binary files /dev/null and b/art/hat-only-mini.png differ diff --git a/art/hat-only.png b/art/hat-only.png new file mode 100644 index 0000000..5ba39e3 Binary files /dev/null and b/art/hat-only.png differ diff --git a/art/hat-only.svg b/art/hat-only.svg new file mode 100644 index 0000000..f0a958b --- /dev/null +++ b/art/hat-only.svg @@ -0,0 +1,52 @@ + + + + + + + + + + diff --git a/art/header.xcf b/art/header.xcf deleted file mode 100644 index 91c9f63..0000000 Binary files a/art/header.xcf and /dev/null differ diff --git a/art/header_1400x500.png b/art/header_1400x500.png deleted file mode 100644 index 196dac7..0000000 Binary files a/art/header_1400x500.png and /dev/null differ diff --git a/art/logo-color2.png b/art/logo-color2.png new file mode 100644 index 0000000..5427504 Binary files /dev/null and b/art/logo-color2.png differ diff --git a/art/logo-color2.svg b/art/logo-color2.svg new file mode 100644 index 0000000..50ac19a --- /dev/null +++ b/art/logo-color2.svg @@ -0,0 +1,78 @@ + + + +StoryOpenTeller diff --git a/art/logo.png b/art/logo.png new file mode 100644 index 0000000..200d4a8 Binary files /dev/null and b/art/logo.png differ diff --git a/art/logo.svg b/art/logo.svg new file mode 100644 index 0000000..7531e7c --- /dev/null +++ b/art/logo.svg @@ -0,0 +1,88 @@ + + + + + + + + + Story + Open + Teller + + + + diff --git a/art/osb-logo-scale320x240.png b/art/osb-logo-scale320x240.png deleted file mode 100644 index aafca90..0000000 Binary files a/art/osb-logo-scale320x240.png and /dev/null differ diff --git a/art/osb-logo.kra b/art/osb-logo.kra deleted file mode 100644 index fd4e835..0000000 Binary files a/art/osb-logo.kra and /dev/null differ diff --git a/art/osb-logo.png b/art/osb-logo.png deleted file mode 100644 index e74c29f..0000000 Binary files a/art/osb-logo.png and /dev/null differ diff --git a/software/chip32/chip32_assembler.cpp b/software/chip32/chip32_assembler.cpp index 3ef4a75..1021720 100644 --- a/software/chip32/chip32_assembler.cpp +++ b/software/chip32/chip32_assembler.cpp @@ -55,7 +55,7 @@ static const uint32_t NbRegs = sizeof(AllRegs) / sizeof(AllRegs[0]); // Keep same order than the opcodes list!! static const std::string Mnemonics[] = { "nop", "halt", "syscall", "lcons", "mov", "push", "pop", "store", "load", "add", "sub", "mul", "div", - "shiftl", "shiftr", "ishiftr", "and", "or", "xor", "not", "call", "ret", "jump", "skipz", "skipnz" + "shiftl", "shiftr", "ishiftr", "and", "or", "xor", "not", "call", "ret", "jump", "jumpr", "skipz", "skipnz" }; static OpCode OpCodes[] = OPCODES_LIST; @@ -196,6 +196,7 @@ bool Assembler::CompileMnemonicArguments(Instr &instr) case OP_SKIPZ: case OP_SKIPNZ: case OP_CALL: + case OP_JUMPR: GET_REG(instr.args[0], ra); instr.compiledArgs.push_back(ra); break; diff --git a/software/chip32/chip32_vm.c b/software/chip32/chip32_vm.c index 374beb6..ef2aabc 100644 --- a/software/chip32/chip32_vm.c +++ b/software/chip32/chip32_vm.c @@ -354,6 +354,13 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx) ctx->registers[PC] = _NEXT_SHORT - 1; break; } + case OP_JUMPR: + { + const uint8_t reg = _NEXT_BYTE; + _CHECK_REGISTER_VALID(reg) + ctx->registers[PC] = ctx->registers[reg] - 1; + break; + } case OP_SKIPZ: case OP_SKIPNZ: { diff --git a/software/chip32/chip32_vm.h b/software/chip32/chip32_vm.h index 98c8dd0..cc97d77 100644 --- a/software/chip32/chip32_vm.h +++ b/software/chip32/chip32_vm.h @@ -76,8 +76,9 @@ typedef enum OP_CALL = 20, ///< set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00 OP_RET = 21, ///< return to the address of last callee (RA), e.g.: ret OP_JUMP = 22, ///< jump to address (can use label or address), e.g.: jump .my_label - OP_SKIPZ = 23, ///< skip next instruction if zero, e.g.: skipz r0 - OP_SKIPNZ = 24, ///< skip next instruction if not zero, e.g.: skipnz r2 + OP_JUMPR = 23, ///< jump to address contained in a register, e.g.: jumpr t9 + OP_SKIPZ = 24, ///< skip next instruction if zero, e.g.: skipz r0 + OP_SKIPNZ = 25, ///< skip next instruction if not zero, e.g.: skipnz r2 INSTRUCTION_COUNT } chip32_instruction_t; @@ -153,7 +154,7 @@ typedef struct { { OP_STORE, 3, 4 }, { OP_LOAD, 3, 4 }, { OP_ADD, 2, 2 }, { OP_SUB, 2, 2 }, { OP_MUL, 2, 2 }, \ { OP_DIV, 2, 2 }, { OP_SHL, 2, 2 }, { OP_SHR, 2, 2 }, { OP_ISHR, 2, 2 }, { OP_AND, 2, 2 }, \ { OP_OR, 2, 2 }, { OP_XOR, 2, 2 }, { OP_NOT, 1, 1 }, { OP_CALL, 1, 1 }, { OP_RET, 0, 0 }, \ -{ OP_JUMP, 1, 2 }, { OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 } } +{ OP_JUMP, 1, 2 }, { OP_JUMPR, 1, 1 }, { OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 } } /** Whole memory is 64KB diff --git a/story-editor/scripts/media.asm b/story-editor/scripts/media.asm index 74d7c86..112e139 100644 --- a/story-editor/scripts/media.asm +++ b/story-editor/scripts/media.asm @@ -8,6 +8,7 @@ ; t1: increment 1 ; t2: increment 4 ; t3: current media address + ; t4: where to jump when OK button is pressed .media_loop_start: load t0, @r0, 4 ; Le premier élément est le nombre de choix possibles, t0 = 3 (exemple) @@ -18,12 +19,27 @@ add t3, t2 ; @++ -; ------- On appelle un autre media node +; --------- We call a media transition node push r0 ; save r0 load r0, @t3, 4 ; r0 = content in ram at address in T4 - call r0 + call r0 ; call subroutine + + ; Return argument in R0: the address of the node to call whe OK is pressed + ; save it in t4 + mov t4, r0 pop r0 - ; TODO: wait for event + + ; wait for event (OK or wheel) + syscall 2 + ; Event is stored in R0 + + ; ----- Test if event is OK button + lcons r1, 1 ; mask for OK button + and r1, r0 ; r1 = r1 AND r0 + skipz r1 ; not OK, skip jump + jumpr t4 ; we do not plan to return here, so a jump is enough + + ; all other events mean: next node sub t0, t1 ; i-- skipnz t0 ; if (r0) goto start_loop; diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index 5e14569..7e53a8d 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -243,6 +243,7 @@ MainWindow::MainWindow() if ((m_dbg.run_result == VM_WAIT_EVENT) && (m_dbg.running)) { + m_dbg.run_result = VM_OK; // Continue execution of node m_runTimer->start(100); } @@ -478,7 +479,8 @@ bool MainWindow::event(QEvent *event) { // Result event is in R1 m_chip32_ctx.registers[R1] = 0x01; - stepInstruction(); + m_dbg.run_result = VM_OK; + m_runTimer->start(100); } } @@ -494,16 +496,27 @@ uint8_t MainWindow::Syscall(uint8_t code) qDebug() << "SYSCALL: " << (int)code; // Media - if (code == 1) + if (code == 1) // Execute media { - // image file name address is in R0 - QString imageFile = m_model.BuildFullImagePath(GetFileNameFromMemory(m_chip32_ctx.registers[R0])); - // sound file name address is in R1 - QString soundFile = m_model.BuildFullSoundPath(GetFileNameFromMemory(m_chip32_ctx.registers[R1])); + if (m_chip32_ctx.registers[R0] != 0) + { + // image file name address is in R0 + QString imageFile = m_model.BuildFullImagePath(GetFileNameFromMemory(m_chip32_ctx.registers[R0])); + qDebug() << "Image: " << imageFile; + m_ostHmiDock->SetImage(imageFile); + } + else + { + m_ostHmiDock->ClearImage(); + } - qDebug() << "Image: " << imageFile << ", Sound: " << soundFile; - m_ostHmiDock->SetImage(imageFile); - m_model.PlaySound(soundFile); + if (m_chip32_ctx.registers[R1] != 0) + { + // sound file name address is in R1 + QString soundFile = m_model.BuildFullSoundPath(GetFileNameFromMemory(m_chip32_ctx.registers[R1])); + qDebug() << ", Sound: " << soundFile; + m_model.PlaySound(soundFile); + } retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause } // WAIT EVENT bits: @@ -513,7 +526,7 @@ uint8_t MainWindow::Syscall(uint8_t code) // 3: pause button // 4: rotary left // 5: rotary right - else if (code == 2) + else if (code == 2) // Wait for event { // Event mask is located in R0 // optional timeout is located in R1 diff --git a/story-editor/src/media_node_model.cpp b/story-editor/src/media_node_model.cpp index 9b4ed37..9c6d30e 100644 --- a/story-editor/src/media_node_model.cpp +++ b/story-editor/src/media_node_model.cpp @@ -64,6 +64,22 @@ MediaNodeModel::MediaNodeModel(StoryGraphModel &model) }; m_mediaData.merge_patch(StoryNodeBase::ToJson()); + + QtNodes::NodeStyle _nodeStyle; + + QColor bgColor = QColor(94, 94, 94); + _nodeStyle.GradientColor0 = bgColor; + _nodeStyle.GradientColor1 = bgColor; + _nodeStyle.GradientColor2 = bgColor; + _nodeStyle.GradientColor3 = bgColor; + _nodeStyle.NormalBoundaryColor = bgColor; + _nodeStyle.FontColor = QColor(206, 206, 206); + _nodeStyle.FontColorFaded = QColor(125, 125, 125); + _nodeStyle.ShadowColor = QColor(20, 20, 20); + _nodeStyle.ConnectionPointColor = QColor(125, 125, 125); + _nodeStyle.FilledConnectionPointColor = QColor(206, 206, 206); + + setNodeStyle(_nodeStyle); } QString MediaNodeModel::caption() const @@ -131,29 +147,65 @@ std::string MediaNodeModel::GenerateConstants() s += StoryProject::FileToConstant(sound); } - // FIXME: Generate choice table if needed (out ports > 1) - std::unordered_set conns = m_model.allConnectionIds(getNodeId()); - - int nb_out_ports = 0; - - for (auto & c : conns) + int nb_out_conns = ComputeOutputConnections(); + if (nb_out_conns > 1) { - if (c.outNodeId > 0) + // Generate choice table if needed (out ports > 1) + std::stringstream ss; + std::string label = ChoiceLabel(); + ss << "$" << label + << " DC32, " + << nb_out_conns << ", "; + + std::unordered_set conns = m_model.allConnectionIds(getNodeId()); + int i = 0; + for (auto & c : conns) { - nb_out_ports++; + std::stringstream ssChoice; + + // On va chercher le label d'entrée du noeud connecté à l'autre bout + ss << m_model.GetNodeEntryLabel(c.inNodeId); + if (i < (nb_out_conns - 1)) + { + ss << ", "; + } + else + { + ss << "\n"; + } + i++; } + + s += ss.str(); } return s; } +std::string MediaNodeModel::ChoiceLabel() const +{ + std::stringstream ss; + ss << "mediaChoice" << std::setw(4) << std::setfill('0') << getNodeId(); + return ss.str(); +} + std::string MediaNodeModel::Build() { std::stringstream ss; + int nb_out_conns = ComputeOutputConnections(); - ss << R"(; ---------------- )" << GetNodeTitle() << "\n"; + ss << R"(; ---------------------------- )" + << GetNodeTitle() + << " Type: " + << (nb_out_conns == 0 ? "End" : nb_out_conns == 1 ? "Transition" : "Choice") + << "\n"; std::string image = StoryProject::RemoveFileExtension(m_mediaData["image"].get()); std::string sound = StoryProject::RemoveFileExtension(m_mediaData["sound"].get()); + + // Le label de ce noeud est généré de la façon suivante : + // "media" + Node ID + id du noeud parent. Si pas de noeud parent, alors rien + ss << EntryLabel() << ":\n"; + if (image.size() > 0) { ss << "lcons r0, $" << image << "\n"; @@ -174,53 +226,54 @@ std::string MediaNodeModel::Build() // Call the media executor (image, sound) ss << "syscall 1\n"; - NodeId id = getNodeId(); - std::unordered_set conns = m_model.allConnectionIds(id); + // Check output connections number + // == 0: end node : generate halt + // == 1: transition node : image + sound on demand, jump directly to the other node when OK + // > 1 : choice node : call the node choice manager - int nb_out_ports = 0; - - for (auto & c : conns) - { - if (c.inNodeId == id) - { - nb_out_ports++; - } - } - - if (nb_out_ports == 0) + if (nb_out_conns == 0) // End node { ss << "halt\n"; } - else + else if (nb_out_conns == 1) // Transition node { + std::unordered_set conns = m_model.allConnectionIds(getNodeId()); + auto it = conns.begin(); + ++it; + // On place dans R0 le prochain noeud à exécuter en cas de OK + ss << "lcons r0, " + << m_model.GetNodeEntryLabel(it->inNodeId) << "\n" + << "ret\n"; + } + else // Choice node + { + ss << "lcons r0, $" << ChoiceLabel() << "\n" + << "jump .media ; no return possible, so a jump is enough"; } - - // Check output connections number - // == 0: end, generate halt - // == 1: jump directly to the other node - // > 1 : call the node choice manager - -// lcons r0, $ChoiceObject -// jump .media ; no return possible, so a jump is enough - - -/* - - - - syscall 1 - lcons r0, $ChoiceObject - jump .media ; no return possible, so a jump is enough -*/ return ss.str(); } -void MediaNodeModel::SetOutPortCount(int count) { +int MediaNodeModel::ComputeOutputConnections() +{ + NodeId id = getNodeId(); + std::unordered_set conns = m_model.allConnectionIds(id); - // m_ui.spinBox->blockSignals(true); + int nb_out_conns = 0; + + for (auto & c : conns) + { + if (c.outNodeId == id) + { + nb_out_conns++; + } + } + return nb_out_conns; +} + +void MediaNodeModel::SetOutPortCount(int count) +{ m_ui.spinBox->setValue(count); - // m_ui.spinBox->blockSignals(true); } unsigned int MediaNodeModel::nPorts(PortType portType) const @@ -286,3 +339,10 @@ void MediaNodeModel::setInData(std::shared_ptr nodeData, PortIndex con Q_EMIT dataUpdated(0); } + +std::string MediaNodeModel::EntryLabel() const +{ + std::stringstream ss; + ss << ".mediaEntry" << std::setw(4) << std::setfill('0') << getNodeId(); + return ss.str(); +} diff --git a/story-editor/src/media_node_model.h b/story-editor/src/media_node_model.h index d31cb3c..6c9eab4 100644 --- a/story-editor/src/media_node_model.h +++ b/story-editor/src/media_node_model.h @@ -63,6 +63,7 @@ public: bool resizable() const override { return true; } + virtual std::string EntryLabel() const override; protected: bool eventFilter(QObject *object, QEvent *event) override; @@ -80,4 +81,6 @@ private: nlohmann::json m_mediaData; void setImage(const QString &fileName); + int ComputeOutputConnections(); + std::string ChoiceLabel() const; }; diff --git a/story-editor/src/osthmi_dock.cpp b/story-editor/src/osthmi_dock.cpp index 54b589f..9680954 100644 --- a/story-editor/src/osthmi_dock.cpp +++ b/story-editor/src/osthmi_dock.cpp @@ -15,3 +15,8 @@ void OstHmiDock::SetImage(const QString &fileName) { m_uiOstDisplay.display->setPixmap(QPixmap(fileName)); } + +void OstHmiDock::ClearImage() +{ + m_uiOstDisplay.display->clear(); +} diff --git a/story-editor/src/osthmi_dock.h b/story-editor/src/osthmi_dock.h index 1a8ac3f..74238ce 100644 --- a/story-editor/src/osthmi_dock.h +++ b/story-editor/src/osthmi_dock.h @@ -11,6 +11,7 @@ public: OstHmiDock(); void SetImage(const QString &fileName); + void ClearImage(); signals: void sigOkButton(); diff --git a/story-editor/src/story_graph_model.cpp b/story-editor/src/story_graph_model.cpp index b9b1abd..0d2600c 100644 --- a/story-editor/src/story_graph_model.cpp +++ b/story-editor/src/story_graph_model.cpp @@ -523,7 +523,7 @@ std::string StoryGraphModel::Build() NodeId firstNode = FindFirstNode(); - chip32 << "\tjump .entry\r\n"; + chip32 << "\tjump " << GetNodeEntryLabel(firstNode) << "\r\n"; // First generate all constants for (auto const nodeId : allNodeIds()) @@ -535,27 +535,30 @@ std::string StoryGraphModel::Build() } } - - nlohmann::json nodesJsonArray; for (auto const nodeId : allNodeIds()) { auto it = _models.find(nodeId); - if (it == _models.end()) - return ""; - - auto &model = it->second; - if (model->getNodeId() == firstNode) + if (it != _models.end()) { - chip32 << ".entry:\r\n"; + chip32 << it->second->Build() << "\n"; } - - chip32 << model->Build() << "\n"; } return chip32.str(); } +std::string StoryGraphModel::GetNodeEntryLabel(NodeId nodeId) const +{ + std::string label; + auto it = _models.find(nodeId); + if (it != _models.end()) + { + label = it->second->EntryLabel(); + } + return label; +} + void StoryGraphModel::addPort(NodeId nodeId, PortType portType, PortIndex portIndex) { // STAGE 1. diff --git a/story-editor/src/story_graph_model.h b/story-editor/src/story_graph_model.h index 7dd669e..e26ead2 100644 --- a/story-editor/src/story_graph_model.h +++ b/story-editor/src/story_graph_model.h @@ -176,6 +176,8 @@ public: std::string Build(); + ///< Returns a node entry name + std::string GetNodeEntryLabel(NodeId nodeId) const; // Centralized for wide usage void PlaySound(const QString &fileName); diff --git a/story-editor/src/story_node_base.h b/story-editor/src/story_node_base.h index b0c465b..11c6e0f 100644 --- a/story-editor/src/story_node_base.h +++ b/story-editor/src/story_node_base.h @@ -60,6 +60,8 @@ public: return ""; } + virtual std::string EntryLabel() const = 0; + NodeGeometryData &geometryData() { return m_geometryData; } std::string GetNodeTitle() const { return m_nodeTitle; }