From 9990affa8eb3029dc25d371d52bff7793b80dd4c Mon Sep 17 00:00:00 2001 From: w3ndo Date: Thu, 12 Mar 2026 19:30:12 +0300 Subject: [PATCH 1/5] chore(develop/scaffold) - added basic documentation for scaffolding with ecsx - created the bones of a scaffold generator --- guides/scaffolds_guide.md | 13 +++++++++ lib/mix/tasks/ecsx.gen.scaffold.ex | 43 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 guides/scaffolds_guide.md create mode 100644 lib/mix/tasks/ecsx.gen.scaffold.ex diff --git a/guides/scaffolds_guide.md b/guides/scaffolds_guide.md new file mode 100644 index 0000000..b4878ce --- /dev/null +++ b/guides/scaffolds_guide.md @@ -0,0 +1,13 @@ +# Scaffold Guides + +Sometimes you will be developing component and system pairs that are often developed together. + +A good example of these pairs are position components and a movement system. In a 2D space, you will have an `XPosition` and `YPosition`( and probably `Xvelocity` and `YVelocity` )component and a movement system that updates these components. + +By using a scaffold, you will now be able to create these component system pairs. For instance; + +``` +mix ecsx.gen.scaffold movement2d +``` + +would generate these components and systems for movement on a 2d plane. \ No newline at end of file diff --git a/lib/mix/tasks/ecsx.gen.scaffold.ex b/lib/mix/tasks/ecsx.gen.scaffold.ex new file mode 100644 index 0000000..21a6ec4 --- /dev/null +++ b/lib/mix/tasks/ecsx.gen.scaffold.ex @@ -0,0 +1,43 @@ +defmodule Mix.Tasks.Ecsx.Gen.Sacffold do + @shortdoc "Generates a scaffold for common system and component combinations like movement systems" + + @moduledoc """ + Scaffolds a system and component pair for an ECSx appliaction + + $ mix ecxs.gen.scaffold movement2d + + The argument is the type of scaffold to be generated + + Valid scaffolds are: + * movement2d + """ + + use Mix.Task + + alias Mix.Tasks.ECSx.Helpers + alias Mix.Tasks.ECSx.Gen.System + alias Mix.Tasks.ECSx.Gen.Component + + @doc false + def run([]) do + "Invalid arguments." + |> message_with_help() + |> Mix.raise() + end + + def run([scaffold_type | _] = args) do + create_scaffold(scaffold_type) + end + + defp create_scaffold(scaffold_type) do + + end + + defp create_associated_scaffold_systems(scaffold_type) do + + end + + defp create_associated_scaffol_systems(scaffold_type) do + + end +end From c3cc0ca37bf47a899b189ba160c38a4651e3735a Mon Sep 17 00:00:00 2001 From: w3ndo Date: Thu, 12 Mar 2026 20:00:12 +0300 Subject: [PATCH 2/5] feat(develop/scaffold) - added tests for scaffold generator --- guides/scaffolds_guide.md | 2 +- lib/mix/tasks/ecsx.gen.scaffold.ex | 35 +++++++++++++----- mix.lock | 2 +- test/mix/tasks/ecsx.gen.scaffold_test.exs | 44 +++++++++++++++++++++++ 4 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 test/mix/tasks/ecsx.gen.scaffold_test.exs diff --git a/guides/scaffolds_guide.md b/guides/scaffolds_guide.md index b4878ce..9a70fc6 100644 --- a/guides/scaffolds_guide.md +++ b/guides/scaffolds_guide.md @@ -2,7 +2,7 @@ Sometimes you will be developing component and system pairs that are often developed together. -A good example of these pairs are position components and a movement system. In a 2D space, you will have an `XPosition` and `YPosition`( and probably `Xvelocity` and `YVelocity` )component and a movement system that updates these components. +A good example of these pairs are position components and a movement system. In a 2D space, you will have an `XPosition` and `YPosition`( and probably `Xvelocity` and `YVelocity` )component and a movement system that updates these components. Other exanmples would be health/damage systems, where your entity has health/damage point components and a health system updates their values. By using a scaffold, you will now be able to create these component system pairs. For instance; diff --git a/lib/mix/tasks/ecsx.gen.scaffold.ex b/lib/mix/tasks/ecsx.gen.scaffold.ex index 21a6ec4..e58f3ed 100644 --- a/lib/mix/tasks/ecsx.gen.scaffold.ex +++ b/lib/mix/tasks/ecsx.gen.scaffold.ex @@ -1,4 +1,4 @@ -defmodule Mix.Tasks.Ecsx.Gen.Sacffold do +defmodule Mix.Tasks.Ecsx.Gen.Scaffold do @shortdoc "Generates a scaffold for common system and component combinations like movement systems" @moduledoc """ @@ -18,26 +18,45 @@ defmodule Mix.Tasks.Ecsx.Gen.Sacffold do alias Mix.Tasks.ECSx.Gen.System alias Mix.Tasks.ECSx.Gen.Component + @valid_scaffold_types ~w(movement2d health collision) + @doc false def run([]) do - "Invalid arguments." + "Invalid arguments - must provide a valid scaffold type" |> message_with_help() |> Mix.raise() end - def run([scaffold_type | _] = args) do - create_scaffold(scaffold_type) + + def run([scaffold_type | opts] = args) do + scaffold_type = validate(scaffold_type) + {opts, _, _} = OptionParser.parse(opts, strict: [namespace: :string]) + create_scaffold(scaffold_type, opts) end - defp create_scaffold(scaffold_type) do + defp message_with_help(message) do + """ + #{message} - end + mix ecsx.gen.sacffold expects a scaffold type. - defp create_associated_scaffold_systems(scaffold_type) do + For example: + mix ecsx.gen.scaffold movement2d + + """ end - defp create_associated_scaffol_systems(scaffold_type) do + defp validate(scaffold_type) when scaffold_type in @valid_scaffold_types, do: String.to_atom(scaffold_type) + + defp validate(_), do: Mix.raise("Invalid scaffold type. Possible scaffold types are: #{inspect(@valid_scaffold_types)}}") + + defp create_scaffold(scaffold_type, opts) do end + + defp create_associated_scaffold_system(:movement2d, namespace \\ nil), do: nil + + defp create_associated_scaffold_components(:movement2d, namespace \\ nil), do: nil + end diff --git a/mix.lock b/mix.lock index cf7c6cb..f6426c0 100644 --- a/mix.lock +++ b/mix.lock @@ -19,7 +19,7 @@ "mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, diff --git a/test/mix/tasks/ecsx.gen.scaffold_test.exs b/test/mix/tasks/ecsx.gen.scaffold_test.exs new file mode 100644 index 0000000..7931636 --- /dev/null +++ b/test/mix/tasks/ecsx.gen.scaffold_test.exs @@ -0,0 +1,44 @@ +Code.require_file("../../support/mix_helper.exs", __DIR__) + +defmodule Mix.Tasks.Ecsx.Gen.ScaffoldTest do + use ExUnit.Case + + import ECSx.MixHelper + + setup do + create_sample_ecsx_project() + on_exit(&clean_tmp_dir/0) + :ok + end + + test "generates system for :movement2d scaffold" do + true + end + + test "generates components for :movement2d scaffold" do + true + end + + test "injects generated system into manager" do + true + end + + test "injects generated component(s) into manager" do + true + end + + test "fails with invalid arguments" do + Mix.Project.in_project(:my_app, ".", fn _module -> + # missing scaffold type + assert_raise(Mix.Error, fn -> + Mix.Tasks.Ecsx.Gen.Scaffold.run([]) + end) + + # invalid scaffold type + assert_raise(Mix.Error, fn -> + Mix.Tasks.Ecsx.Gen.Scaffold.run(["movement3d"]) + end) + end) + end + +end From e52f10405d75dbe3ed6fad0f90c1cf1c42ec354d Mon Sep 17 00:00:00 2001 From: w3ndo Date: Fri, 13 Mar 2026 12:07:43 +0300 Subject: [PATCH 3/5] feat(develop/scaffold) - We can scaffold a movement system and movement components for 2d --- lib/mix/tasks/ecsx.gen.scaffold.ex | 36 +++--- test/mix/tasks/ecsx.gen.scaffold_test.exs | 143 ++++++++++++++++++++-- 2 files changed, 158 insertions(+), 21 deletions(-) diff --git a/lib/mix/tasks/ecsx.gen.scaffold.ex b/lib/mix/tasks/ecsx.gen.scaffold.ex index e58f3ed..794ffd1 100644 --- a/lib/mix/tasks/ecsx.gen.scaffold.ex +++ b/lib/mix/tasks/ecsx.gen.scaffold.ex @@ -10,13 +10,14 @@ defmodule Mix.Tasks.Ecsx.Gen.Scaffold do Valid scaffolds are: * movement2d + * health + * collision """ use Mix.Task - alias Mix.Tasks.ECSx.Helpers - alias Mix.Tasks.ECSx.Gen.System - alias Mix.Tasks.ECSx.Gen.Component + alias Mix.Tasks.Ecsx.Gen.System + alias Mix.Tasks.Ecsx.Gen.Component @valid_scaffold_types ~w(movement2d health collision) @@ -27,11 +28,10 @@ defmodule Mix.Tasks.Ecsx.Gen.Scaffold do |> Mix.raise() end - - def run([scaffold_type | opts] = args) do + def run([scaffold_type | _opts] = _args) do scaffold_type = validate(scaffold_type) - {opts, _, _} = OptionParser.parse(opts, strict: [namespace: :string]) - create_scaffold(scaffold_type, opts) + # {opts, _, _} = OptionParser.parse(opts, strict: [namespace: :string]) + create_scaffold(scaffold_type) end defp message_with_help(message) do @@ -47,16 +47,24 @@ defmodule Mix.Tasks.Ecsx.Gen.Scaffold do """ end - defp validate(scaffold_type) when scaffold_type in @valid_scaffold_types, do: String.to_atom(scaffold_type) - - defp validate(_), do: Mix.raise("Invalid scaffold type. Possible scaffold types are: #{inspect(@valid_scaffold_types)}}") + defp validate(scaffold_type) when scaffold_type in @valid_scaffold_types, + do: String.to_atom(scaffold_type) - defp create_scaffold(scaffold_type, opts) do + defp validate(_), + do: + Mix.raise( + "Invalid scaffold type. Possible scaffold types are: #{inspect(@valid_scaffold_types)}}" + ) + defp create_scaffold(scaffold_type) do + create_associated_scaffold_system(scaffold_type) + create_associated_scaffold_components(scaffold_type) end - defp create_associated_scaffold_system(:movement2d, namespace \\ nil), do: nil - - defp create_associated_scaffold_components(:movement2d, namespace \\ nil), do: nil + defp create_associated_scaffold_system(:movement2d), do: System.run(["Movement2d"]) + defp create_associated_scaffold_components(:movement2d) do + ["XPosition", "YPosition", "XVelocity", "YVelocity"] + |> Enum.each(fn component -> Component.run([component, "float"]) end) + end end diff --git a/test/mix/tasks/ecsx.gen.scaffold_test.exs b/test/mix/tasks/ecsx.gen.scaffold_test.exs index 7931636..600914b 100644 --- a/test/mix/tasks/ecsx.gen.scaffold_test.exs +++ b/test/mix/tasks/ecsx.gen.scaffold_test.exs @@ -12,19 +12,149 @@ defmodule Mix.Tasks.Ecsx.Gen.ScaffoldTest do end test "generates system for :movement2d scaffold" do - true - end + Mix.Project.in_project(:my_app, ".", fn _module -> + Mix.Tasks.Ecsx.Gen.Scaffold.run(["movement2d"]) - test "generates components for :movement2d scaffold" do - true + system_file = File.read!("lib/my_app/systems/movement2d.ex") + + assert system_file == + """ + defmodule MyApp.Systems.Movement2d do + @moduledoc \"\"\" + Documentation for Movement2d system. + \"\"\" + @behaviour ECSx.System + + @impl ECSx.System + def run do + # System logic + :ok + end + end + """ + end) end test "injects generated system into manager" do - true + Mix.Project.in_project(:my_app, ".", fn _module -> + Mix.Tasks.Ecsx.Gen.Scaffold.run(["movement2d"]) + + manager_file = File.read!("lib/my_app/manager.ex") + + assert manager_file == + """ + defmodule MyApp.Manager do + @moduledoc \"\"\" + ECSx manager. + \"\"\" + use ECSx.Manager + + def setup do + # Seed persistent components only for the first server start + # (This will not be run on subsequent app restarts) + :ok + end + + def startup do + # Load ephemeral components during first server start and again + # on every subsequent app restart + :ok + end + + # Declare all valid Component types + def components do + [ + MyApp.Components.YVelocity, + MyApp.Components.XVelocity, + MyApp.Components.YPosition, + MyApp.Components.XPosition + ] + end + + # Declare all Systems to run + def systems do + [ + MyApp.Systems.Movement2d + ] + end + end + """ + end) end test "injects generated component(s) into manager" do - true + Mix.Project.in_project(:my_app, ".", fn _module -> + Mix.Tasks.Ecsx.Gen.Scaffold.run(["movement2d"]) + + manager_file = File.read!("lib/my_app/manager.ex") + + assert manager_file == + """ + defmodule MyApp.Manager do + @moduledoc \"\"\" + ECSx manager. + \"\"\" + use ECSx.Manager + + def setup do + # Seed persistent components only for the first server start + # (This will not be run on subsequent app restarts) + :ok + end + + def startup do + # Load ephemeral components during first server start and again + # on every subsequent app restart + :ok + end + + # Declare all valid Component types + def components do + [ + MyApp.Components.YVelocity, + MyApp.Components.XVelocity, + MyApp.Components.YPosition, + MyApp.Components.XPosition + ] + end + + # Declare all Systems to run + def systems do + [ + MyApp.Systems.Movement2d + ] + end + end + """ + end) + end + + test "generates components for :movement2d scaffold" do + Mix.Project.in_project(:my_app, ".", fn _module -> + Mix.Tasks.Ecsx.Gen.Scaffold.run(["movement2d"]) + + expected_files = [ + "lib/my_app/components/x_position.ex", + "lib/my_app/components/y_position.ex", + "lib/my_app/components/x_velocity.ex", + "lib/my_app/components/y_velocity.ex" + ] + + assert true = Enum.all?(Enum.map(expected_files, fn path -> File.exists?(path) end)) + + x_position_components_file = File.read!("lib/my_app/components/x_position.ex") + + assert x_position_components_file == + """ + defmodule MyApp.Components.XPosition do + @moduledoc \"\"\" + Documentation for XPosition components. + \"\"\" + use ECSx.Component, + value: :float + end + """ + end) end test "fails with invalid arguments" do @@ -40,5 +170,4 @@ defmodule Mix.Tasks.Ecsx.Gen.ScaffoldTest do end) end) end - end From 8dd13bcce22962951f1f2bcc20159208cac3404b Mon Sep 17 00:00:00 2001 From: w3ndo Date: Fri, 13 Mar 2026 12:49:04 +0300 Subject: [PATCH 4/5] feat(develop/scaffold) - new templates for movement2d system and health system when scaffolded --- guides/scaffolds_guide.md | 17 ++++- lib/mix/tasks/ecsx.gen.scaffold.ex | 9 +++ lib/mix/tasks/ecsx.gen.system.ex | 14 +++- priv/templates/scaffold_templates/health.ex | 24 ++++++ .../scaffold_templates/movement2d.ex | 26 +++++++ test/mix/tasks/ecsx.gen.scaffold_test.exs | 76 +++++++++++++++++++ 6 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 priv/templates/scaffold_templates/health.ex create mode 100644 priv/templates/scaffold_templates/movement2d.ex diff --git a/guides/scaffolds_guide.md b/guides/scaffolds_guide.md index 9a70fc6..3e0dd73 100644 --- a/guides/scaffolds_guide.md +++ b/guides/scaffolds_guide.md @@ -10,4 +10,19 @@ By using a scaffold, you will now be able to create these component system pairs mix ecsx.gen.scaffold movement2d ``` -would generate these components and systems for movement on a 2d plane. \ No newline at end of file +would generate these components and systems for movement on a 2d plane. + +## Movement2d +``` +mix ecsx.gen.scaffold movement2d +``` + +- This creates 4 components, all with the `float` data type +``` + - x_position + - y_position + - x_velocity + - y_velocity +``` + +and a single system, `movement2d` \ No newline at end of file diff --git a/lib/mix/tasks/ecsx.gen.scaffold.ex b/lib/mix/tasks/ecsx.gen.scaffold.ex index 794ffd1..35cb966 100644 --- a/lib/mix/tasks/ecsx.gen.scaffold.ex +++ b/lib/mix/tasks/ecsx.gen.scaffold.ex @@ -61,10 +61,19 @@ defmodule Mix.Tasks.Ecsx.Gen.Scaffold do create_associated_scaffold_components(scaffold_type) end + # create systems defp create_associated_scaffold_system(:movement2d), do: System.run(["Movement2d"]) + defp create_associated_scaffold_system(:health), do: System.run(["Health"]) + + # create components defp create_associated_scaffold_components(:movement2d) do ["XPosition", "YPosition", "XVelocity", "YVelocity"] |> Enum.each(fn component -> Component.run([component, "float"]) end) end + + defp create_associated_scaffold_components(:health) do + ["HealthPoints", "DamagePoints"] + |> Enum.each(fn component -> Component.run([component, "integer"]) end) + end end diff --git a/lib/mix/tasks/ecsx.gen.system.ex b/lib/mix/tasks/ecsx.gen.system.ex index da06dd7..2fcc972 100644 --- a/lib/mix/tasks/ecsx.gen.system.ex +++ b/lib/mix/tasks/ecsx.gen.system.ex @@ -35,7 +35,19 @@ defmodule Mix.Tasks.Ecsx.Gen.System do defp create_system_file(system_name) do filename = Macro.underscore(system_name) target = "lib/#{Helpers.otp_app()}/systems/#{filename}.ex" - source = Application.app_dir(:ecsx, "/priv/templates/system.ex") + + source = + case system_name do + "Movement2d" -> + Application.app_dir(:ecsx, "/priv/templates/scaffold_templates/movement2d.ex") + + "Health" -> + Application.app_dir(:ecsx, "/priv/templates/scaffold_templates/health.ex") + + _ -> + Application.app_dir(:ecsx, "/priv/templates/system.ex") + end + binding = [app_name: Helpers.root_module(), system_name: system_name] Mix.Generator.create_file(target, EEx.eval_file(source, binding)) diff --git a/priv/templates/scaffold_templates/health.ex b/priv/templates/scaffold_templates/health.ex new file mode 100644 index 0000000..d14d804 --- /dev/null +++ b/priv/templates/scaffold_templates/health.ex @@ -0,0 +1,24 @@ +defmodule <%= app_name %>.Systems.Health do + @moduledoc """ + Documentation for <%= system_name %> system. + """ + @behaviour ECSx.System + + @impl ECSx.System + def run do + # System logic + :ok + end + + # These functions can be used to update the health of entities. + + defp take_damage(entity, damage_points) do + health = <%= app_name %>.Components.HealthPoints.get(entity) + <%= app_name %>.Components.HealthPoints.update(health - damage_points) + end + + defp add_health(entity, extra_health_points) do + health = <%= app_name %>.Components.HealthPoints.get(entity) + <%= app_name %>.Components.HealthPoints.update(health + extra_health_points) + end +end diff --git a/priv/templates/scaffold_templates/movement2d.ex b/priv/templates/scaffold_templates/movement2d.ex new file mode 100644 index 0000000..0e1cc7f --- /dev/null +++ b/priv/templates/scaffold_templates/movement2d.ex @@ -0,0 +1,26 @@ +defmodule <%= app_name %>.Systems.Movement2d do + @moduledoc """ + Documentation for <%= system_name %> system. + """ + @behaviour ECSx.System + + @impl ECSx.System + def run do + # System logic + :ok + end + + # These functions can be used to update the position of entities. + + defp move_x(entity) do + x_pos = <%= app_name %>.Components.XPosition.get(entity) + x_velocity = <%= app_name %>.Components.XVelocity.get(entity) + <%= app_name %>.Components.XPosition.update(x_pos + x_velocity) + end + + defp move_x(entity) do + y_pos = <%= app_name %>.Components.YPosition.get(entity) + y_velocity = <%= app_name %>.Components.YVelocity.get(entity) + <%= app_name %>.Components.YPosition.update(x_pos + x_velocity) + end +end diff --git a/test/mix/tasks/ecsx.gen.scaffold_test.exs b/test/mix/tasks/ecsx.gen.scaffold_test.exs index 600914b..f7477e5 100644 --- a/test/mix/tasks/ecsx.gen.scaffold_test.exs +++ b/test/mix/tasks/ecsx.gen.scaffold_test.exs @@ -30,6 +30,56 @@ defmodule Mix.Tasks.Ecsx.Gen.ScaffoldTest do # System logic :ok end + + # These functions can be used to update the position of entities. + + defp move_x(entity) do + x_pos = MyApp.Components.XPosition.get(entity) + x_velocity = MyApp.Components.XVelocity.get(entity) + MyApp.Components.XPosition.update(x_pos + x_velocity) + end + + defp move_x(entity) do + y_pos = MyApp.Components.YPosition.get(entity) + y_velocity = MyApp.Components.YVelocity.get(entity) + MyApp.Components.YPosition.update(x_pos + x_velocity) + end + end + """ + end) + end + + test "generates system for :health scaffold" do + Mix.Project.in_project(:my_app, ".", fn _module -> + Mix.Tasks.Ecsx.Gen.Scaffold.run(["health"]) + + system_file = File.read!("lib/my_app/systems/health.ex") + + assert system_file == + """ + defmodule MyApp.Systems.Health do + @moduledoc \"\"\" + Documentation for Health system. + \"\"\" + @behaviour ECSx.System + + @impl ECSx.System + def run do + # System logic + :ok + end + + # These functions can be used to update the health of entities. + + defp take_damage(entity, damage_points) do + health = MyApp.Components.HealthPoints.get(entity) + MyApp.Components.HealthPoints.update(health - damage_points) + end + + defp add_health(entity, extra_health_points) do + health = MyApp.Components.HealthPoints.get(entity) + MyApp.Components.HealthPoints.update(health + extra_health_points) + end end """ end) @@ -157,6 +207,32 @@ defmodule Mix.Tasks.Ecsx.Gen.ScaffoldTest do end) end + test "generates components for :health scaffold" do + Mix.Project.in_project(:my_app, ".", fn _module -> + Mix.Tasks.Ecsx.Gen.Scaffold.run(["health"]) + + expected_files = [ + "lib/my_app/components/health_points.ex", + "lib/my_app/components/damage_points.ex" + ] + + assert true = Enum.all?(Enum.map(expected_files, fn path -> File.exists?(path) end)) + + health_points_components_file = File.read!("lib/my_app/components/health_points.ex") + + assert health_points_components_file == + """ + defmodule MyApp.Components.HealthPoints do + @moduledoc \"\"\" + Documentation for HealthPoints components. + \"\"\" + use ECSx.Component, + value: :integer + end + """ + end) + end + test "fails with invalid arguments" do Mix.Project.in_project(:my_app, ".", fn _module -> # missing scaffold type From 90440560bc91cfedb64caddaa67be4e91aff2e2b Mon Sep 17 00:00:00 2001 From: w3ndo Date: Fri, 13 Mar 2026 12:57:34 +0300 Subject: [PATCH 5/5] chore(develop/scaffold) - Updated documentation for scaffolds --- guides/scaffolds_guide.md | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/guides/scaffolds_guide.md b/guides/scaffolds_guide.md index 3e0dd73..bbebe2d 100644 --- a/guides/scaffolds_guide.md +++ b/guides/scaffolds_guide.md @@ -1,10 +1,10 @@ # Scaffold Guides -Sometimes you will be developing component and system pairs that are often developed together. +Some component and system pairs are often developed together. -A good example of these pairs are position components and a movement system. In a 2D space, you will have an `XPosition` and `YPosition`( and probably `Xvelocity` and `YVelocity` )component and a movement system that updates these components. Other exanmples would be health/damage systems, where your entity has health/damage point components and a health system updates their values. +A good example of these pairs are position components and a movement system. In a 2D space, you will have an `XPosition`, `YPosition`,`Xvelocity` and `YVelocity` component and a movement system that updates these components. Other examples would be health/damage systems, where your entity has health/damage point components and a health system updates their values. -By using a scaffold, you will now be able to create these component system pairs. For instance; +By using a scaffold, you will now be able to generate these component system pairs. For instance; ``` mix ecsx.gen.scaffold movement2d @@ -12,7 +12,9 @@ mix ecsx.gen.scaffold movement2d would generate these components and systems for movement on a 2d plane. -## Movement2d + +## Existing scaffolds +### Movement2d ``` mix ecsx.gen.scaffold movement2d ``` @@ -25,4 +27,23 @@ mix ecsx.gen.scaffold movement2d - y_velocity ``` -and a single system, `movement2d` \ No newline at end of file +and a single system, `movement2d` + +The movement system will come with 2 private functions, `move_x` and `move_y` that can be used in the run function to update the entitiy's position. + +### Health +``` +mix ecsx.gen.scaffold health +``` + +This creates a `health_points` component, with an `integer` data type and a single system, `health` + +The health system has 2 private functions, `take_damage/2` and `add_health/2` which can be used to reduce health and increase health of an entity. + +## Scaffold Systems in Progress +### Collision +``` +mix ecsx.gen.scaffold collision +``` + +These allow collisions to be added to entities. It also implements the collision force calculations for you. \ No newline at end of file