From 67fd6bc8f4ae337c11ce347f6caed15a4c0371e6 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 11:27:06 -0800 Subject: [PATCH 01/14] added onNodeFinish --- packages/flame_jenny/jenny/lib/src/dialogue_view.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_view.dart b/packages/flame_jenny/jenny/lib/src/dialogue_view.dart index e944e7faebc..360a87918fa 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_view.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_view.dart @@ -44,6 +44,14 @@ abstract class DialogueView { /// to complete before proceeding with the actual dialogue. FutureOr onNodeStart(Node node) {} + /// Called when the dialogue exits the [node]. + /// + /// For example, during a `<>` this callback will be called with the + /// current node, and then [onNodeStart] will be called with the new node. + /// Similarly, the command `<>` will trigger this callback too. At the + /// same time, during `<>` this callback will not be invoked. + FutureOr onNodeFinish(Node node) {} + /// Called when the next dialogue [line] should be presented to the user. /// /// The [DialogueView] may decide to present the [line] in whatever way it From c9b09f38b814fccb6abe3339513dd2ab60dd18b7 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 11:29:09 -0800 Subject: [PATCH 02/14] rename runNode() -> startDialogue() --- .../jenny/runtime/dialogue_runner.md | 2 +- .../jenny/runtime/dialogue_view.md | 5 ++--- .../jenny/lib/src/dialogue_runner.dart | 4 ++-- .../jenny/test/dialogue_runner_test.dart | 18 +++++++++--------- .../jenny/test/dialogue_view_test.dart | 4 ++-- .../structure/commands/jump_command_test.dart | 2 +- .../commands/user_defined_command_test.dart | 4 ++-- .../flame_jenny/jenny/test/test_scenario.dart | 2 +- 8 files changed, 20 insertions(+), 21 deletions(-) diff --git a/doc/other_modules/jenny/runtime/dialogue_runner.md b/doc/other_modules/jenny/runtime/dialogue_runner.md index 5754db2ca7b..73b09a3a5bb 100644 --- a/doc/other_modules/jenny/runtime/dialogue_runner.md +++ b/doc/other_modules/jenny/runtime/dialogue_runner.md @@ -36,7 +36,7 @@ The constructor takes two required parameters: ## Methods -**runNode**(`String nodeName`) +**startDialogue**(`String nodeName`) : Executes the node with the given name, and returns a future that completes only when the dialogue finishes running (which may be a while). A single `DialogueRunner` can only run one node at a time. diff --git a/doc/other_modules/jenny/runtime/dialogue_view.md b/doc/other_modules/jenny/runtime/dialogue_view.md index 6ec7475ecc7..0046b7add2b 100644 --- a/doc/other_modules/jenny/runtime/dialogue_view.md +++ b/doc/other_modules/jenny/runtime/dialogue_view.md @@ -39,9 +39,8 @@ simultaneously). : Called when the dialogue is about to finish. **onNodeStart**(`Node node`) -: Called when the dialogue runner starts executing the [Node]. This will be called at the start of - `DialogueView.runNode()` (but after the **onDialogueStart**), and then each time the dialogue - jumps to another node. +: Called when the dialogue runner starts executing the [Node]. This will be called right after the + **onDialogueStart** event, and then each time the dialogue jumps to another node. This method is a good place to perform node-specific initialization, for example by querying the `node`'s properties or metadata. diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart index 64b7b406add..473c6e47ea9 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart @@ -47,7 +47,7 @@ class DialogueRunner { /// Executes the node with the given name, and returns a future that finishes /// once the dialogue stops running. - Future runNode(String nodeName) async { + Future startDialogue(String nodeName) async { try { if (_currentNodes.isNotEmpty) { throw DialogueError( @@ -158,7 +158,7 @@ class DialogueRunner { @internal Future jumpToNode(String nodeName) async { _finishCurrentNode(); - return runNode(nodeName); + return startDialogue(nodeName); } @internal diff --git a/packages/flame_jenny/jenny/test/dialogue_runner_test.dart b/packages/flame_jenny/jenny/test/dialogue_runner_test.dart index 8bb2f495eda..ab2ecf34b77 100644 --- a/packages/flame_jenny/jenny/test/dialogue_runner_test.dart +++ b/packages/flame_jenny/jenny/test/dialogue_runner_test.dart @@ -27,7 +27,7 @@ void main() { ); final view = _RecordingDialogueView(); final dialogue = DialogueRunner(yarnProject: yarn, dialogueViews: [view]); - await dialogue.runNode('Hamlet'); + await dialogue.startDialogue('Hamlet'); expect( view.events, [ @@ -93,7 +93,7 @@ void main() { yarnProject: yarn, dialogueViews: [view1, view2, view3], ); - await dialogue.runNode('The Robot and the Mattress'); + await dialogue.startDialogue('The Robot and the Mattress'); expect(events, [ '[A] onDialogueStart()', '[B] onDialogueStart()', @@ -130,7 +130,7 @@ void main() { '===\n'); final view = _RecordingDialogueView(choices: [1]); final dialogue = DialogueRunner(yarnProject: yarn, dialogueViews: [view]); - await dialogue.runNode('X'); + await dialogue.startDialogue('X'); expect( view.events, [ @@ -149,7 +149,7 @@ void main() { view.events.clear(); view.choices.add(0); - await dialogue.runNode('X'); + await dialogue.startDialogue('X'); expect( view.events, [ @@ -174,11 +174,11 @@ void main() { final view = _RecordingDialogueView(choices: [2, 1]); final dialogue = DialogueRunner(yarnProject: yarn, dialogueViews: [view]); await expectLater( - () => dialogue.runNode('A'), + () => dialogue.startDialogue('A'), hasDialogueError('Invalid option index chosen in a dialogue: 2'), ); await expectLater( - () => dialogue.runNode('A'), + () => dialogue.startDialogue('A'), hasDialogueError( 'A dialogue view selected a disabled option: ' 'Option(Only two #disabled)', @@ -193,7 +193,7 @@ void main() { dialogueViews: [_SimpleDialogueView(), _SimpleDialogueView()], ); expect( - () => dialogue.runNode('A'), + () => dialogue.startDialogue('A'), hasDialogueError( 'No dialogue views capable of making a dialogue choice', ), @@ -327,9 +327,9 @@ void main() { ); final view = _RecordingDialogueView(); final dialogue = DialogueRunner(yarnProject: yarn, dialogueViews: [view]); - unawaited(dialogue.runNode('Start')); + unawaited(dialogue.startDialogue('Start')); expect( - () => dialogue.runNode('Other'), + () => dialogue.startDialogue('Other'), hasDialogueError( 'Cannot run node "Other" because another node is currently running: ' '"Start"', diff --git a/packages/flame_jenny/jenny/test/dialogue_view_test.dart b/packages/flame_jenny/jenny/test/dialogue_view_test.dart index 8e6d8c6bbdc..493c2fc4f59 100644 --- a/packages/flame_jenny/jenny/test/dialogue_view_test.dart +++ b/packages/flame_jenny/jenny/test/dialogue_view_test.dart @@ -31,7 +31,7 @@ void main() { yarnProject: yarn, dialogueViews: [view1, view2], ); - await dialogueRunner.runNode('Start'); + await dialogueRunner.startDialogue('Start'); expect( view2.events, const [ @@ -68,7 +68,7 @@ void main() { yarnProject: yarn, dialogueViews: [view1, view2, view3], ); - await dialogueRunner.runNode('Start'); + await dialogueRunner.startDialogue('Start'); expect( view2.events, const [ diff --git a/packages/flame_jenny/jenny/test/structure/commands/jump_command_test.dart b/packages/flame_jenny/jenny/test/structure/commands/jump_command_test.dart index 5079f9cacae..65cb6f07762 100644 --- a/packages/flame_jenny/jenny/test/structure/commands/jump_command_test.dart +++ b/packages/flame_jenny/jenny/test/structure/commands/jump_command_test.dart @@ -114,7 +114,7 @@ void main() { '===\n', ); expect( - () => DialogueRunner(yarnProject: yarn, dialogueViews: []).runNode('A'), + () => DialogueRunner(yarnProject: yarn, dialogueViews: []).startDialogue('A'), hasNameError('NameError: Node "Up" could not be found'), ); }); diff --git a/packages/flame_jenny/jenny/test/structure/commands/user_defined_command_test.dart b/packages/flame_jenny/jenny/test/structure/commands/user_defined_command_test.dart index a2aff4c021f..8edb93ffceb 100644 --- a/packages/flame_jenny/jenny/test/structure/commands/user_defined_command_test.dart +++ b/packages/flame_jenny/jenny/test/structure/commands/user_defined_command_test.dart @@ -142,7 +142,7 @@ void main() { dialogueViews: [view1, view2], ); - await dialogueRunner.runNode('Start'); + await dialogueRunner.startDialogue('Start'); expect(fnCounter, 1); expect(view1.numCalled, 1); expect(view1.argumentString, 'a b 1'); @@ -151,7 +151,7 @@ void main() { expect(view2.argumentString, 'a b 1'); expect(view2.arguments, ['a', 'b', '1']); - await dialogueRunner.runNode('Start'); + await dialogueRunner.startDialogue('Start'); expect(fnCounter, 2); expect(view2.numCalled, 2); expect(view2.argumentString, 'a b 2'); diff --git a/packages/flame_jenny/jenny/test/test_scenario.dart b/packages/flame_jenny/jenny/test/test_scenario.dart index e03fb872edc..13061447632 100644 --- a/packages/flame_jenny/jenny/test/test_scenario.dart +++ b/packages/flame_jenny/jenny/test/test_scenario.dart @@ -30,7 +30,7 @@ Future testScenario({ yarnProject: yarnProject, dialogueViews: [plan], ); - await dialogue.runNode(plan.startNode); + await dialogue.startDialogue(plan.startNode); assert( plan.done, '\n' From 402a71954e865e2c7b0f8394253b98f304c41af9 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 11:54:53 -0800 Subject: [PATCH 03/14] refactor --- .../jenny/lib/src/dialogue_runner.dart | 66 +++++++++++-------- .../structure/commands/jump_command_test.dart | 3 +- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart index 473c6e47ea9..6ed04c5b38f 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart @@ -44,45 +44,55 @@ class DialogueRunner { final List _currentNodes; final List _iterators; _LineDeliveryPipeline? _linePipeline; + String? _initialNode; /// Executes the node with the given name, and returns a future that finishes /// once the dialogue stops running. Future startDialogue(String nodeName) async { try { - if (_currentNodes.isNotEmpty) { + if (_initialNode != null) { throw DialogueError( 'Cannot run node "$nodeName" because another node is ' - 'currently running: "${_currentNodes.last.title}"', + 'currently running: "$_initialNode"', ); } - final newNode = project.nodes[nodeName]; - if (newNode == null) { - throw NameError('Node "$nodeName" could not be found'); - } + _initialNode = nodeName; _dialogueViews.forEach((dv) => dv.dialogueRunner = this); - _currentNodes.add(newNode); - _iterators.add(newNode.iterator); - await _combineFutures( - [for (final view in _dialogueViews) view.onDialogueStart()], - ); - await _combineFutures( - [for (final view in _dialogueViews) view.onNodeStart(newNode)], - ); - - while (_iterators.isNotEmpty) { - final iterator = _iterators.last; - if (iterator.moveNext()) { - final entry = iterator.current; - await entry.processInDialogueRunner(this); - } else { - _finishCurrentNode(); - } - } - await _combineFutures( - [for (final view in _dialogueViews) view.onDialogueFinish()], - ); + await _event((view) => view.onDialogueStart()); + await _runNode(nodeName); + await _event((view) => view.onDialogueFinish()); } finally { _dialogueViews.forEach((dv) => dv.dialogueRunner = null); + _initialNode = null; + } + } + + FutureOr _event(FutureOr Function(DialogueView v) callback) { + return _combineFutures([ + for (final view in _dialogueViews) callback(view) + ]); + } + + Future _runNode(String nodeName) async { + final node = project.nodes[nodeName]; + if (node == null) { + throw NameError('Node "$nodeName" could not be found'); + } + + _currentNodes.add(node); + _iterators.add(node.iterator); + + await _combineFutures( + [for (final view in _dialogueViews) view.onNodeStart(node)], + ); + while (_iterators.isNotEmpty) { + final iterator = _iterators.last; + if (iterator.moveNext()) { + final entry = iterator.current; + await entry.processInDialogueRunner(this); + } else { + _finishCurrentNode(); + } } } @@ -158,7 +168,7 @@ class DialogueRunner { @internal Future jumpToNode(String nodeName) async { _finishCurrentNode(); - return startDialogue(nodeName); + return _runNode(nodeName); } @internal diff --git a/packages/flame_jenny/jenny/test/structure/commands/jump_command_test.dart b/packages/flame_jenny/jenny/test/structure/commands/jump_command_test.dart index 65cb6f07762..12841a408df 100644 --- a/packages/flame_jenny/jenny/test/structure/commands/jump_command_test.dart +++ b/packages/flame_jenny/jenny/test/structure/commands/jump_command_test.dart @@ -114,7 +114,8 @@ void main() { '===\n', ); expect( - () => DialogueRunner(yarnProject: yarn, dialogueViews: []).startDialogue('A'), + () => DialogueRunner(yarnProject: yarn, dialogueViews: []) + .startDialogue('A'), hasNameError('NameError: Node "Up" could not be found'), ); }); From 3c2462b7bd73af50471b0f26b98cd926d7862a04 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 12:54:13 -0800 Subject: [PATCH 04/14] _currentNodes -> _currentNode --- .../jenny/lib/src/dialogue_runner.dart | 51 ++++++++++--------- .../functions/visit_count_test.dart | 3 ++ 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart index 6ed04c5b38f..f71aaf9db18 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart @@ -36,55 +36,53 @@ class DialogueRunner { required List dialogueViews, }) : project = yarnProject, _dialogueViews = dialogueViews, - _currentNodes = [], _iterators = []; final YarnProject project; final List _dialogueViews; - final List _currentNodes; final List _iterators; _LineDeliveryPipeline? _linePipeline; - String? _initialNode; + Node? _currentNode; + String? _initialNodeName; /// Executes the node with the given name, and returns a future that finishes /// once the dialogue stops running. Future startDialogue(String nodeName) async { try { - if (_initialNode != null) { + if (_initialNodeName != null) { throw DialogueError( 'Cannot run node "$nodeName" because another node is ' - 'currently running: "$_initialNode"', + 'currently running: "$_initialNodeName"', ); } - _initialNode = nodeName; - _dialogueViews.forEach((dv) => dv.dialogueRunner = this); + _initialNodeName = nodeName; + _dialogueViews.forEach((view) { + if (view.dialogueRunner != null) { + throw DialogueError( + 'DialogueView is currently attached to another DialogueRunner', + ); + } + view.dialogueRunner = this; + }); await _event((view) => view.onDialogueStart()); await _runNode(nodeName); await _event((view) => view.onDialogueFinish()); } finally { _dialogueViews.forEach((dv) => dv.dialogueRunner = null); - _initialNode = null; + _initialNodeName = null; } } - FutureOr _event(FutureOr Function(DialogueView v) callback) { - return _combineFutures([ - for (final view in _dialogueViews) callback(view) - ]); - } - Future _runNode(String nodeName) async { final node = project.nodes[nodeName]; if (node == null) { throw NameError('Node "$nodeName" could not be found'); } - _currentNodes.add(node); + _currentNode = node; _iterators.add(node.iterator); - await _combineFutures( - [for (final view in _dialogueViews) view.onNodeStart(node)], - ); + await _event((view) => view.onNodeStart(node)); while (_iterators.isNotEmpty) { final iterator = _iterators.last; if (iterator.moveNext()) { @@ -98,13 +96,13 @@ class DialogueRunner { void _finishCurrentNode() { // Increment visit count for the node - assert(_currentNodes.isNotEmpty); - final nodeVariable = '@${_currentNodes.last.title}'; + assert(_currentNode != null); + final nodeVariable = '@${_currentNode!.title}'; project.variables.setVariable( nodeVariable, project.variables.getNumericValue(nodeVariable) + 1, ); - _currentNodes.removeLast(); + _currentNode = null; _iterators.removeLast(); } @@ -145,9 +143,7 @@ class DialogueRunner { if (!chosenOption.isAvailable) { _error('A dialogue view selected a disabled option: $chosenOption'); } - await _combineFutures( - [for (final view in _dialogueViews) view.onChoiceFinish(chosenOption)], - ); + await _event((view) => view.onChoiceFinish(chosenOption)); enterBlock(chosenOption.block); } @@ -173,7 +169,6 @@ class DialogueRunner { @internal void stop() { - _currentNodes.clear(); _iterators.clear(); } @@ -190,6 +185,12 @@ class DialogueRunner { } } + FutureOr _event(FutureOr Function(DialogueView) callback) { + return _combineFutures([ + for (final view in _dialogueViews) callback(view) + ]); + } + Never _error(String message) { stop(); throw DialogueError(message); diff --git a/packages/flame_jenny/jenny/test/structure/expressions/functions/visit_count_test.dart b/packages/flame_jenny/jenny/test/structure/expressions/functions/visit_count_test.dart index e325f8d578f..3fbeb5cc0d1 100644 --- a/packages/flame_jenny/jenny/test/structure/expressions/functions/visit_count_test.dart +++ b/packages/flame_jenny/jenny/test/structure/expressions/functions/visit_count_test.dart @@ -18,6 +18,9 @@ void main() { jumping... <> <> + // Also let's make sure <> doesn't prevent calculation + // of node visits + // <> === ''', testPlan: ''' From 9db8e3ccb2d539a4cb0d6a2775688a44b14a1e19 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 13:01:09 -0800 Subject: [PATCH 05/14] _iterators -> _currentIterator --- .../jenny/lib/src/dialogue_runner.dart | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart index f71aaf9db18..e7ead0319d2 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart @@ -35,18 +35,17 @@ class DialogueRunner { required YarnProject yarnProject, required List dialogueViews, }) : project = yarnProject, - _dialogueViews = dialogueViews, - _iterators = []; + _dialogueViews = dialogueViews; final YarnProject project; final List _dialogueViews; - final List _iterators; _LineDeliveryPipeline? _linePipeline; Node? _currentNode; + NodeIterator? _currentIterator; String? _initialNodeName; - /// Executes the node with the given name, and returns a future that finishes - /// once the dialogue stops running. + /// Starts the dialogue with the node [nodeName], and returns a future that + /// finishes once the dialogue stops running. Future startDialogue(String nodeName) async { try { if (_initialNodeName != null) { @@ -80,13 +79,12 @@ class DialogueRunner { } _currentNode = node; - _iterators.add(node.iterator); + _currentIterator = node.iterator; await _event((view) => view.onNodeStart(node)); - while (_iterators.isNotEmpty) { - final iterator = _iterators.last; - if (iterator.moveNext()) { - final entry = iterator.current; + while (_currentIterator != null) { + if (_currentIterator!.moveNext()) { + final entry = _currentIterator!.current; await entry.processInDialogueRunner(this); } else { _finishCurrentNode(); @@ -103,7 +101,7 @@ class DialogueRunner { project.variables.getNumericValue(nodeVariable) + 1, ); _currentNode = null; - _iterators.removeLast(); + _currentIterator = null; } void sendSignal(dynamic signal) { @@ -158,7 +156,7 @@ class DialogueRunner { @internal void enterBlock(Block block) { - _iterators.last.diveInto(block); + _currentIterator!.diveInto(block); } @internal @@ -169,7 +167,7 @@ class DialogueRunner { @internal void stop() { - _iterators.clear(); + _currentIterator = null; } FutureOr _combineFutures(List> maybeFutures) { From ab204f319993d83b7e1f76f361244b1c803168cf Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 13:12:47 -0800 Subject: [PATCH 06/14] refactor --- .../jenny/lib/src/dialogue_runner.dart | 58 +++++++++---------- .../src/structure/commands/jump_command.dart | 4 +- .../src/structure/commands/stop_command.dart | 2 +- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart index e7ead0319d2..e261f7e9874 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart @@ -43,6 +43,7 @@ class DialogueRunner { Node? _currentNode; NodeIterator? _currentIterator; String? _initialNodeName; + String? _nextNode; /// Starts the dialogue with the node [nodeName], and returns a future that /// finishes once the dialogue stops running. @@ -64,7 +65,10 @@ class DialogueRunner { view.dialogueRunner = this; }); await _event((view) => view.onDialogueStart()); - await _runNode(nodeName); + _nextNode = nodeName; + while (_nextNode != null) { + await _runNode(_nextNode!); + } await _event((view) => view.onDialogueFinish()); } finally { _dialogueViews.forEach((dv) => dv.dialogueRunner = null); @@ -78,30 +82,26 @@ class DialogueRunner { throw NameError('Node "$nodeName" could not be found'); } + _nextNode = null; _currentNode = node; _currentIterator = node.iterator; await _event((view) => view.onNodeStart(node)); - while (_currentIterator != null) { - if (_currentIterator!.moveNext()) { - final entry = _currentIterator!.current; - await entry.processInDialogueRunner(this); - } else { - _finishCurrentNode(); - } + while (_currentIterator?.moveNext() ?? false) { + final entry = _currentIterator!.current; + await entry.processInDialogueRunner(this); } + _incrementNodeVisitCount(); + _currentNode = null; + _currentIterator = null; } - void _finishCurrentNode() { - // Increment visit count for the node - assert(_currentNode != null); + void _incrementNodeVisitCount() { final nodeVariable = '@${_currentNode!.title}'; project.variables.setVariable( nodeVariable, project.variables.getNumericValue(nodeVariable) + 1, ); - _currentNode = null; - _currentIterator = null; } void sendSignal(dynamic signal) { @@ -131,15 +131,21 @@ class DialogueRunner { for (final view in _dialogueViews) view.onChoiceStart(choice) ]; if (futures.every((future) => future == DialogueView.never)) { - _error('No dialogue views capable of making a dialogue choice'); + throw DialogueError( + 'No dialogue views capable of making a dialogue choice', + ); } final chosenIndex = await Future.any(futures); if (chosenIndex < 0 || chosenIndex >= choice.options.length) { - _error('Invalid option index chosen in a dialogue: $chosenIndex'); + throw DialogueError( + 'Invalid option index chosen in a dialogue: $chosenIndex', + ); } final chosenOption = choice.options[chosenIndex]; if (!chosenOption.isAvailable) { - _error('A dialogue view selected a disabled option: $chosenOption'); + throw DialogueError( + 'A dialogue view selected a disabled option: $chosenOption', + ); } await _event((view) => view.onChoiceFinish(chosenOption)); enterBlock(chosenOption.block); @@ -159,15 +165,12 @@ class DialogueRunner { _currentIterator!.diveInto(block); } + /// Stops the current node, and then starts running [nodeName]. If [nodeName] + /// is null, then stops the dialogue completely. @internal - Future jumpToNode(String nodeName) async { - _finishCurrentNode(); - return _runNode(nodeName); - } - - @internal - void stop() { + void jumpToNode(String? nodeName) { _currentIterator = null; + _nextNode = nodeName; } FutureOr _combineFutures(List> maybeFutures) { @@ -184,14 +187,7 @@ class DialogueRunner { } FutureOr _event(FutureOr Function(DialogueView) callback) { - return _combineFutures([ - for (final view in _dialogueViews) callback(view) - ]); - } - - Never _error(String message) { - stop(); - throw DialogueError(message); + return _combineFutures([for (final view in _dialogueViews) callback(view)]); } } diff --git a/packages/flame_jenny/jenny/lib/src/structure/commands/jump_command.dart b/packages/flame_jenny/jenny/lib/src/structure/commands/jump_command.dart index 03b817c16b8..df7a09dc66f 100644 --- a/packages/flame_jenny/jenny/lib/src/structure/commands/jump_command.dart +++ b/packages/flame_jenny/jenny/lib/src/structure/commands/jump_command.dart @@ -11,7 +11,7 @@ class JumpCommand extends Command { String get name => 'jump'; @override - Future execute(DialogueRunner dialogue) { - return dialogue.jumpToNode(target.value); + void execute(DialogueRunner dialogue) { + dialogue.jumpToNode(target.value); } } diff --git a/packages/flame_jenny/jenny/lib/src/structure/commands/stop_command.dart b/packages/flame_jenny/jenny/lib/src/structure/commands/stop_command.dart index 22b5d001774..c93cca23804 100644 --- a/packages/flame_jenny/jenny/lib/src/structure/commands/stop_command.dart +++ b/packages/flame_jenny/jenny/lib/src/structure/commands/stop_command.dart @@ -9,6 +9,6 @@ class StopCommand extends Command { @override void execute(DialogueRunner dialogue) { - dialogue.stop(); + dialogue.jumpToNode(null); } } From 7bb81b0d940447fe8319fd391befe256c6f31617 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 13:18:13 -0800 Subject: [PATCH 07/14] enable onNodeFinish() callback --- packages/flame_jenny/jenny/lib/src/dialogue_runner.dart | 5 +++++ packages/flame_jenny/jenny/test/dialogue_view_test.dart | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart index e261f7e9874..5bbce90b633 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart @@ -92,6 +92,8 @@ class DialogueRunner { await entry.processInDialogueRunner(this); } _incrementNodeVisitCount(); + await _event((view) => view.onNodeFinish(node)); + _currentNode = null; _currentIterator = null; } @@ -167,6 +169,9 @@ class DialogueRunner { /// Stops the current node, and then starts running [nodeName]. If [nodeName] /// is null, then stops the dialogue completely. + /// + /// This command is synchronous, i.e. it does not wait for the node to + /// *actually* finish (which calls the `onNodeFinish` callback). @internal void jumpToNode(String? nodeName) { _currentIterator = null; diff --git a/packages/flame_jenny/jenny/test/dialogue_view_test.dart b/packages/flame_jenny/jenny/test/dialogue_view_test.dart index 493c2fc4f59..eef2b2e2312 100644 --- a/packages/flame_jenny/jenny/test/dialogue_view_test.dart +++ b/packages/flame_jenny/jenny/test/dialogue_view_test.dart @@ -46,6 +46,7 @@ void main() { 'onLineStart(Last line)', 'onLineFinish(Last line)', 'onCommand(<>)', + 'onNodeFinish(Start)', 'onDialogueFinish()', ], ); @@ -78,6 +79,7 @@ void main() { 'onLineSignal(line="First line", signal=)', 'onLineStop(First line)', 'onLineFinish(First line)', + 'onNodeFinish(Start)', 'onDialogueFinish()', ], ); @@ -102,6 +104,11 @@ class _RecordingDialogueView extends DialogueView { events.add('onNodeStart(${node.title})'); } + @override + FutureOr onNodeFinish(Node node) { + events.add('onNodeFinish(${node.title})'); + } + @override FutureOr onLineStart(DialogueLine line) async { events.add('onLineStart(${line.text})'); From 5a6764433485cbeb37a85ce8e77d40221d740f1b Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 13:29:21 -0800 Subject: [PATCH 08/14] docs --- doc/other_modules/jenny/runtime/dialogue_runner.md | 5 +++++ doc/other_modules/jenny/runtime/dialogue_view.md | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/doc/other_modules/jenny/runtime/dialogue_runner.md b/doc/other_modules/jenny/runtime/dialogue_runner.md index 73b09a3a5bb..4746f53a6ae 100644 --- a/doc/other_modules/jenny/runtime/dialogue_runner.md +++ b/doc/other_modules/jenny/runtime/dialogue_runner.md @@ -93,6 +93,11 @@ the sequence of emitted events will be as follows (assuming the second option is - `onNodeFinish(Node("Away"))` - `onDialogueFinish()` +:::{note} +Keep in mind that if a `DialogueError` is thrown while running the dialogue, then it will terminate +immediately and none of the `*Finish` callbacks will run. +::: + [DialogueChoice]: dialogue_choice.md [DialogueView]: dialogue_view.md diff --git a/doc/other_modules/jenny/runtime/dialogue_view.md b/doc/other_modules/jenny/runtime/dialogue_view.md index 0046b7add2b..3d5afcf376b 100644 --- a/doc/other_modules/jenny/runtime/dialogue_view.md +++ b/doc/other_modules/jenny/runtime/dialogue_view.md @@ -46,7 +46,11 @@ simultaneously). `node`'s properties or metadata. **onNodeFinish**(`Node node`) -: TODO +: Called when the dialogue runner finishes executing the [Node], before **onDialogueFinish**. This + will also be called every time a node is exited via `<>` or a `<>` command (including + jumps from node to itself). + + This callback can be used to clean up any preparations that were performed in `onNodeStart`. **onLineStart**(`DialogueLine line`) `-> bool` : Called when the next dialogue [line] should be presented to the user. From 2e60c083a3ad058fb8667999f069ed110ecb7536 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 14:00:48 -0800 Subject: [PATCH 09/14] minor cleanup --- .../jenny/lib/src/dialogue_runner.dart | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart index 5bbce90b633..cdbcc8dc40a 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_runner.dart @@ -73,9 +73,24 @@ class DialogueRunner { } finally { _dialogueViews.forEach((dv) => dv.dialogueRunner = null); _initialNodeName = null; + _nextNode = null; + _currentIterator = null; + _currentNode = null; } } + void sendSignal(dynamic signal) { + assert(_linePipeline != null); + final line = _linePipeline!.line; + for (final view in _dialogueViews) { + view.onLineSignal(line, signal); + } + } + + void stopLine() { + _linePipeline?.stop(); + } + Future _runNode(String nodeName) async { final node = project.nodes[nodeName]; if (node == null) { @@ -106,18 +121,6 @@ class DialogueRunner { ); } - void sendSignal(dynamic signal) { - assert(_linePipeline != null); - final line = _linePipeline!.line; - for (final view in _dialogueViews) { - view.onLineSignal(line, signal); - } - } - - void stopLine() { - _linePipeline?.stop(); - } - @internal Future deliverLine(DialogueLine line) async { final pipeline = _LineDeliveryPipeline(line, _dialogueViews); @@ -178,16 +181,16 @@ class DialogueRunner { _nextNode = nodeName; } + /// Similar to `Future.wait()`, but accepts `FutureOr`s. FutureOr _combineFutures(List> maybeFutures) { - final futures = >[ - for (final maybeFuture in maybeFutures) - if (maybeFuture is Future) maybeFuture - ]; - if (futures.length == 1) { - return futures[0]; - } else if (futures.isNotEmpty) { - final Future result = Future.wait(futures); - return result; + final futures = maybeFutures.whereType>().toList(); + if (futures.isNotEmpty) { + if (futures.length == 1) { + return futures[0]; + } else { + final Future result = Future.wait(futures); + return result; + } } } From 7cd2dac77298aa4e62604dfab7aab472e4e9bafb Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 14:03:36 -0800 Subject: [PATCH 10/14] enable a test --- .../test/structure/expressions/functions/visit_count_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flame_jenny/jenny/test/structure/expressions/functions/visit_count_test.dart b/packages/flame_jenny/jenny/test/structure/expressions/functions/visit_count_test.dart index 3fbeb5cc0d1..ce4e32d9f7a 100644 --- a/packages/flame_jenny/jenny/test/structure/expressions/functions/visit_count_test.dart +++ b/packages/flame_jenny/jenny/test/structure/expressions/functions/visit_count_test.dart @@ -20,7 +20,7 @@ void main() { <> // Also let's make sure <> doesn't prevent calculation // of node visits - // <> + <> === ''', testPlan: ''' From 0fbc7e2995cbfec3e2bb642dafb4a2464c635c74 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 14:09:48 -0800 Subject: [PATCH 11/14] minor --- doc/other_modules/jenny/runtime/dialogue_runner.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/other_modules/jenny/runtime/dialogue_runner.md b/doc/other_modules/jenny/runtime/dialogue_runner.md index 4746f53a6ae..5b2e435e9ea 100644 --- a/doc/other_modules/jenny/runtime/dialogue_runner.md +++ b/doc/other_modules/jenny/runtime/dialogue_runner.md @@ -94,8 +94,8 @@ the sequence of emitted events will be as follows (assuming the second option is - `onDialogueFinish()` :::{note} -Keep in mind that if a `DialogueError` is thrown while running the dialogue, then it will terminate -immediately and none of the `*Finish` callbacks will run. +Keep in mind that if a `DialogueError` is thrown while running the dialogue, then the dialogue will +terminate immediately and none of the `*Finish` callbacks will run. ::: From 19012bbf16bc6a96cc7d2846ee81ab03c795eee4 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 14:12:43 -0800 Subject: [PATCH 12/14] fix title validator --- .github/workflows/title-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/title-validation.yml b/.github/workflows/title-validation.yml index 8a0401936fd..b9b81994f0f 100644 --- a/.github/workflows/title-validation.yml +++ b/.github/workflows/title-validation.yml @@ -29,6 +29,6 @@ jobs: revert style test - subjectPattern: ^[A-Z] + subjectPattern: ^[A-Z].* subjectPatternError: | The subject of the PR must begin with an uppercase letter. From 68bec86fc91b138316ce3381d56cd160bce46edd Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 14:19:04 -0800 Subject: [PATCH 13/14] test --- .github/workflows/title-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/title-validation.yml b/.github/workflows/title-validation.yml index b9b81994f0f..16a3c1fa9c8 100644 --- a/.github/workflows/title-validation.yml +++ b/.github/workflows/title-validation.yml @@ -2,7 +2,7 @@ name: 'PR Title is Conventional' on: - pull_request_target: + pull_request: types: - opened - edited From a138ba43ed6ac0d0408003f1a86e2f91e2797cc5 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 21 Dec 2022 14:19:50 -0800 Subject: [PATCH 14/14] undo test --- .github/workflows/title-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/title-validation.yml b/.github/workflows/title-validation.yml index 16a3c1fa9c8..b9b81994f0f 100644 --- a/.github/workflows/title-validation.yml +++ b/.github/workflows/title-validation.yml @@ -2,7 +2,7 @@ name: 'PR Title is Conventional' on: - pull_request: + pull_request_target: types: - opened - edited