Skip to content

Commit

Permalink
commands
Browse files Browse the repository at this point in the history
  • Loading branch information
mrDIMAS committed Sep 12, 2024
1 parent ba0bab1 commit 67362d1
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 2 deletions.
87 changes: 87 additions & 0 deletions src/code/snippets/src/editor/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use fyrox::{
scene::{base::BaseBuilder, graph::Graph, node::Node, sprite::SpriteBuilder},
script::ScriptTrait,
};
use fyroxed_base::command::{
AddCollectionItemCommand, Command, RemoveCollectionItemCommand, SetPropertyCommand,
};
use fyroxed_base::{
camera::PickingOptions,
command::{CommandContext, CommandTrait},
Expand Down Expand Up @@ -400,3 +403,87 @@ impl CommandTrait for SetPointPositionCommand {
}
}
// ANCHOR_END: command

// ANCHOR: set_point_1
fn set_point_1(node_handle: Handle<Node>, message_sender: &MessageSender) {
message_sender.do_command(SetPropertyCommand::new(
"points[1]".to_string(),
Box::new(Vector3::new(1.0, 2.0, 3.0)),
// Entity getter supplies a reference to the base object, which will be used
// to search on for the property with the specified name.
move |ctx| {
ctx.get_mut::<GameSceneContext>()
.scene
.graph
.node_mut(node_handle)
.try_get_script_mut::<MyScript>()
.unwrap()
},
))
}
// ANCHOR_END: set_point_1

// ANCHOR: add_collection_element
fn add_collection_element(node_handle: Handle<Node>, message_sender: &MessageSender) {
message_sender.do_command(AddCollectionItemCommand::new(
"points".to_string(),
Box::new(Vector3::new(1.0, 2.0, 3.0)),
// Entity getter supplies a reference to the base object, which will be used
// to search on for the property with the specified name.
move |ctx| {
ctx.get_mut::<GameSceneContext>()
.scene
.graph
.node_mut(node_handle)
.try_get_script_mut::<MyScript>()
.unwrap()
},
))
}
// ANCHOR_END: add_collection_element

// ANCHOR: remove_collection_element
fn remove_collection_element(node_handle: Handle<Node>, message_sender: &MessageSender) {
message_sender.do_command(RemoveCollectionItemCommand::new(
"points".to_string(),
1,
// Entity getter supplies a reference to the base object, which will be used
// to search on for the property with the specified name.
move |ctx| {
ctx.get_mut::<GameSceneContext>()
.scene
.graph
.node_mut(node_handle)
.try_get_script_mut::<MyScript>()
.unwrap()
},
))
}
// ANCHOR_END: remove_collection_element

// ANCHOR: command_trait
#[derive(Debug)]
struct ExampleCommand {}

impl CommandTrait for ExampleCommand {
fn name(&mut self, context: &dyn CommandContext) -> String {
// This method is called to get a name for the command which it will show
// in the command stack viewer.
"Command".to_string()
}

fn execute(&mut self, context: &mut dyn CommandContext) {
// This method is called when the editor executes the command.
}

fn revert(&mut self, context: &mut dyn CommandContext) {
// This method is called when the editor undo the command.
}

fn finalize(&mut self, _: &mut dyn CommandContext) {
// This method is called when the command is about to be destroyed.
// Its main use case is mark some resources as free when they were previously
// reserved by `execute` or `revert`. Usually it is for reserved handles in Pool.
}
}
// ANCHOR_END: command_trait
76 changes: 74 additions & 2 deletions src/editor/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,83 @@ The next step is to update the gizmo on each frame:
## Commands

As was mentioned previously, any modification to scene node's content (including scripts) must be done using commands.
Commands encapsulates an "atomic" action, this could be simple property or collection modification or something complex,
that involves heavy calculations and so on. The editor has a command stack that executes incoming commands and saves them
for potential undo. The stack has a top command, when new command is added to the stack, it removes all command prior the
top and makes the new command the top one. Every removed command is finalized (see below).

There are two ways of using commands: use reflection-based command, or use custom command. Reflection-based commands
usually used when you need to set a new value to some property. On the other hand, custom commands could perform complex
actions, that cannot be done using reflection-based command. In our case we'll be using both command types.
actions, that cannot be done using reflection-based command. The previous section contains an example of custom command,
they're quite verbose and require decent amount of boilerplate code.

(TODO)
### Custom Commands

Custom commands is the best way to get better understanding of command system and how it works. This section explains
how to create custom commands and how they're executed. Each command must implement `Command` trait which looks like
this:

```rust
{{#include ../code/snippets/src/editor/plugins.rs:command_trait}}
```

This chapter already showed an example of a custom command:

```rust
{{#include ../code/snippets/src/editor/plugins.rs:command}}
```

The main idea is very simple, `execute` must do the required change and `revert` must undo it. There's one special
method that has very limited use, but it cannot be avoided. `finalize` is used to return reserved resources back to
where they were obtained from. Typically, it is pool handles that can be reserved for further use. If they won't be
returned, pool will have empty unused entries forever.

### Reflection-based Commands

There are three main types of reflection-based commands that can be used to manipulate scene objects:

#### `SetPropertyCommand`

Sets a new value for a property at the given path. This command cannot change the size of collections (add or remove
items), the next two commands are exactly for this (see next subsections). This is how you could use this command to
change position of a point at index 1:

```rust
{{#include ../code/snippets/src/editor/plugins.rs:set_point_1}}
```

The first argument is a path to variable, it could be any "depth" and support enum variants, indices, etc:
`[email protected][123].stuff`. Enum variants are marked by `@` sign. The second argument is a new value for
the property. It could be any object that implements `Reflect` trait, in our case it is `Vector3<f32>`. The last argument
is entity getter function. Its purpose is to provide a reference to an object in which the reflection system will search
for the property with the given name.

#### `AddCollectionItemCommand`

Adds a new collection item command at the given path. The collection could be anything that implements `ReflectList`
trait (`Vec`, `ArrayVec`, custom types) or `ReflectHashMap` trait (`HashMap`, `FxHashMap`, custom types). Typical usage
is something like this:

```rust
{{#include ../code/snippets/src/editor/plugins.rs:add_collection_element}}
```

The meaning of each argument is the same as in `SetPropertyCommand` command.

#### `RemoveCollectionItemCommand`

Removes an item from a collection by the given index. The collection could be anything that implements `ReflectList`
trait (`Vec`, `ArrayVec`, custom types) or `ReflectHashMap` trait (`HashMap`, `FxHashMap`, custom types). In case of
hash maps, the index cannot be used reliably, because hash maps do not have an ability to be randomly indexed. To remove
the exact element at the index, you must ensure that `hash_map.iter().nth(index)` corresponds to the item and only then
use this index in the command. Typical usage is something like this:

```rust
{{#include ../code/snippets/src/editor/plugins.rs:remove_collection_element}}
```

The first argument in this command a name of the collection property, the second - item index, and the third is the
entity getter. See `SetPropertyCommand` for more info.

## Contextual Panels

Expand Down

0 comments on commit 67362d1

Please sign in to comment.