Skip to content

Commit

Permalink
Support the id option for cloning (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
iacobson authored Oct 7, 2023
1 parent 0729303 commit e023d7e
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 13 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v0.7.1 (2023-10-07)

### Improvements

- `Ecspanse.Command.clone_entity!/2` and `Ecspanse.Command.deep_clone_entity!/2` now accept an `:id` option to set the id of the cloned entity.

## v0.7.0 (2023-10-05)

### Breaking
Expand Down
49 changes: 41 additions & 8 deletions lib/ecspanse/command.ex
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,16 @@ defmodule Ecspanse.Command do
to avoid the need to lock all involved components.
> #### Note {: .info}
>
> The entity's `Ecspanse.Component.Children` and `Ecspanse.Component.Parents` components are not cloned.
> Use `deep_clone_entity!/1` to clone the entity and all of its descendants.
> Use `deep_clone_entity!/2` to clone the entity and all of its descendants.
## Options
- `:id` - a custom unique ID for the entity (binary). If not provided, a random UUID will be generated.
> #### Entity ID {: .warning}
> The entity IDs must be unique. Attention when providing the `:id` option.
> If the provided ID is not unique, clonning the entity will raise an error.
## Examples
Expand All @@ -228,8 +235,8 @@ defmodule Ecspanse.Command do
```
"""
@doc group: :entities
@spec clone_entity!(Entity.t()) :: Entity.t()
def clone_entity!(entity) do
@spec clone_entity!(Entity.t(), opts :: keyword()) :: Entity.t()
def clone_entity!(entity, opts \\ []) do
components = Ecspanse.Query.list_components(entity)

component_specs =
Expand All @@ -238,7 +245,15 @@ defmodule Ecspanse.Command do
{component.__struct__, state, Ecspanse.Query.list_tags(component)}
end)

spawn_entity!({Ecspanse.Entity, components: component_specs, children: [], parents: []})
case Keyword.fetch(opts, :id) do
{:ok, entity_id} when is_binary(entity_id) ->
spawn_entity!(
{Ecspanse.Entity, id: entity_id, components: component_specs, children: [], parents: []}
)

_ ->
spawn_entity!({Ecspanse.Entity, components: component_specs, children: [], parents: []})
end
end

@doc """
Expand All @@ -248,16 +263,34 @@ defmodule Ecspanse.Command do
it is recommended to run this function in a synchronous system (such as a `frame_start` or `frame_end` system)
to avoid the need to lock all involved components.
## Options
- `:id` - a custom unique ID for the entity (binary). If not provided, a random UUID will be generated.
> #### Entity ID {: .warning}
> The entity IDs must be unique. Attention when providing the `:id` option.
> If the provided ID is not unique, clonning the entity will raise an error.
The cloned descendants entities will receive a random UUID as ID by default.
## Cloning descendants
The deep clonning operates only for the descendants of the entity.
If any of the descendants has a parent that is not a descendant of the entity,
the parent will not be cloned or referenced.
If this is a desired behaviour, the parents should be added manually after the deep clonning.
## Examples
```elixir
%Ecspanse.Entity{} = entity = Ecspanse.Command.deep_clone_entity!(enemy_entity)
```
"""
@doc group: :entities
@spec deep_clone_entity!(Entity.t()) :: Entity.t()
def deep_clone_entity!(entity) do
cloned_entity = clone_entity!(entity)
@spec deep_clone_entity!(Entity.t(), opts: keyword()) :: Entity.t()
def deep_clone_entity!(entity, opts \\ []) do
cloned_entity = clone_entity!(entity, opts)
children = Ecspanse.Query.list_children(entity)

case children do
Expand Down
4 changes: 4 additions & 0 deletions lib/ecspanse/entity.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ defmodule Ecspanse.Entity do
> #### Note {: .info}
> At least one of the `:components`, `:children` or `:parents` options must be provided,
> otherwise the entity cannot be persisted.
> #### Entity ID {: .warning}
> The entity IDs must be unique. Attention when providing the `:id` option as part of the `entity_spec`.
> If the provided ID is not unique, spwaning entities will raise an error.
"""
@type entity_spec :: {Entity, opts :: keyword()}

Expand Down
2 changes: 1 addition & 1 deletion lib/ecspanse/projection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ defmodule Ecspanse.Projection do
> #### Info {: .info}
> The `attrs` map is passed to the `c:Ecspanse.Projection.project/1`
> and `c:Ecspanse.Projection.on_change/2` callbacks.
> and `c:Ecspanse.Projection.on_change/3` callbacks.
The caller is responsible for storing the returned `pid`.
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Ecspanse.MixProject do
use Mix.Project

@version "0.7.0"
@version "0.7.1"
@source_url "https://github.com/iacobson/ecspanse"

def project do
Expand Down
36 changes: 34 additions & 2 deletions test/ecspanse/command_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ defmodule Ecspanse.CommandTest do
end
end

describe "clone_entity!/1" do
describe "clone_entity!/2" do
test "create a clone of the entity" do
entity =
Ecspanse.Command.spawn_entity!(
Expand Down Expand Up @@ -145,9 +145,22 @@ defmodule Ecspanse.CommandTest do
assert Ecspanse.Query.list_tags(entity_component_1) ==
Ecspanse.Query.list_tags(cloned_entity_component_1)
end

test "create a named clone of an entity" do
entity =
Ecspanse.Command.spawn_entity!(
{Ecspanse.Entity, id: "alpha", components: [TestComponent1]}
)

assert entity.id == "alpha"

clone = Ecspanse.Command.clone_entity!(entity, id: "beta")

assert clone.id == "beta"
end
end

describe "deep_clone_entity/1" do
describe "deep_clone_entity/2" do
test "create a clone of the entity and all its descendants" do
assert %Ecspanse.Entity{} =
root_entity =
Expand All @@ -170,6 +183,25 @@ defmodule Ecspanse.CommandTest do

assert length(root_entity_descendants) == length(cloned_entity_descendants)
end

test "create a named deep clone of an entity" do
assert %Ecspanse.Entity{} =
root_entity =
Ecspanse.Command.spawn_entity!(
{Ecspanse.Entity, id: "alpha", components: [TestComponent1]}
)

entity_1 = Ecspanse.Command.spawn_entity!({Ecspanse.Entity, parents: [root_entity]})
entity_2 = Ecspanse.Command.spawn_entity!({Ecspanse.Entity, parents: [entity_1]})
_entity_3 = Ecspanse.Command.spawn_entity!({Ecspanse.Entity, parents: [entity_2]})
_entity_4 = Ecspanse.Command.spawn_entity!({Ecspanse.Entity, parents: [root_entity]})

assert root_entity.id == "alpha"

cloned_entity = Ecspanse.Command.deep_clone_entity!(root_entity, id: "beta")

assert cloned_entity.id == "beta"
end
end

describe "add_components!/1" do
Expand Down
2 changes: 1 addition & 1 deletion test/ecspanse/projection_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ defmodule Ecspanse.ProjectionTest do
end
end

describe "on_change/2" do
describe "on_change/3" do
test "is called when the projection changes" do
entity =
Ecspanse.Command.spawn_entity!(
Expand Down

0 comments on commit e023d7e

Please sign in to comment.