Begin implementing GameCoordinator
This commit is contained in:
parent
4e629fb8e4
commit
4a56f9b954
@ -26,6 +26,7 @@ defmodule Amethyst.Application do
|
||||
children = [
|
||||
{Task.Supervisor, name: Amethyst.ConnectionSupervisor},
|
||||
{Amethyst.Keys, 1024},
|
||||
{Amethyst.GameRegistry, []}
|
||||
]
|
||||
|
||||
children = case Application.fetch_env!(:amethyst, :port) do
|
||||
|
30
apps/amethyst/lib/api/game.ex
Normal file
30
apps/amethyst/lib/api/game.ex
Normal file
@ -0,0 +1,30 @@
|
||||
defmodule Amethyst.API.Game do
|
||||
@moduledoc """
|
||||
This module includes the interface for defining and registering
|
||||
a game with Amethyst.
|
||||
"""
|
||||
@callback instantiate() :: {:ok, new_state :: term} | {:error, reason :: term}
|
||||
|
||||
defmacro __using__(opts) do
|
||||
meta = Keyword.get(opts, :meta, [])
|
||||
quote do
|
||||
@behaviour Amethyst.API.Game
|
||||
def child_spec(state) do
|
||||
%{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, []}
|
||||
}
|
||||
end
|
||||
def start_link() do
|
||||
register_self()
|
||||
_loop()
|
||||
end
|
||||
defp _loop() do
|
||||
_loop()
|
||||
end
|
||||
def register_self() do
|
||||
Amethyst.GameRegistry.register(__MODULE__, unquote(meta))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
36
apps/amethyst/lib/apps/game_coordinator.ex
Normal file
36
apps/amethyst/lib/apps/game_coordinator.ex
Normal file
@ -0,0 +1,36 @@
|
||||
defmodule Amethyst.GameCoordinator do
|
||||
use GenServer
|
||||
|
||||
@moduledoc """
|
||||
The game coordinator is responsible for keeping track of active
|
||||
instances of each game and can create new ones on demand.
|
||||
"""
|
||||
|
||||
@impl true
|
||||
def init(initial) do
|
||||
{:ok, initial}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:get, type}, _from, state) do
|
||||
{:reply, find_or_create(type, state), state}
|
||||
end
|
||||
|
||||
defp create(mod) do
|
||||
state = mod.initialize()
|
||||
spawn(mod, :listen, [state])
|
||||
end
|
||||
|
||||
defp find(type, games) do
|
||||
existing = games |> Enum.filter(fn {mod, _pid, _opts} -> mod == type end)
|
||||
[{_mod, pid, _} | _] = existing
|
||||
pid
|
||||
end
|
||||
|
||||
defp find_or_create(type, games) do
|
||||
case find(type, games) do
|
||||
nil -> create(type)
|
||||
some -> some
|
||||
end
|
||||
end
|
||||
end
|
53
apps/amethyst/lib/apps/game_registry.ex
Normal file
53
apps/amethyst/lib/apps/game_registry.ex
Normal file
@ -0,0 +1,53 @@
|
||||
defmodule Amethyst.GameRegistry do
|
||||
use GenServer
|
||||
|
||||
@moduledoc """
|
||||
The game registry stores information about which games are
|
||||
available.
|
||||
"""
|
||||
|
||||
@impl true
|
||||
def init(initial) do
|
||||
{:ok, initial}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:register, module, meta}, _, state) do
|
||||
if Keyword.get(meta, :default, false) && find_default(state) != nil do
|
||||
{:reply, :error, :default_exists}
|
||||
end
|
||||
{:reply, :ok, [{module, meta} | state]}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:list}, _, state) do
|
||||
{:reply, state, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:get_default}, _, state) do
|
||||
{:reply, find_default(state), state}
|
||||
end
|
||||
|
||||
defp find_default(state) do
|
||||
state |> Enum.filter(fn {_mod, meta} ->
|
||||
Keyword.get(meta, :default, false)
|
||||
end) |> List.first(nil)
|
||||
end
|
||||
|
||||
def start_link(initial) when is_list(initial) do
|
||||
GenServer.start_link(__MODULE__, initial, name: {:global, __MODULE__})
|
||||
end
|
||||
|
||||
def register(module, meta) do
|
||||
GenServer.call({:global, __MODULE__}, {:register, module, meta})
|
||||
end
|
||||
|
||||
def list() do
|
||||
GenServer.call({:global, __MODULE__}, {:list})
|
||||
end
|
||||
|
||||
def get_default() do
|
||||
GenServer.call({:global, __MODULE__}, {:get_default})
|
||||
end
|
||||
end
|
@ -332,6 +332,11 @@ defmodule Amethyst.Server.Configuration do
|
||||
0, false, ["minecraft:overworld"], 0, 16, 16, false, true, true, 0,
|
||||
"minecraft:overworld", <<0::64>>, :spectator, nil, false, true, nil, 0, false
|
||||
}, client)
|
||||
game = Amethyst.GameRegistry.get_default()
|
||||
if game == nil do
|
||||
raise RuntimeError, "No game is set as a default! A single game must be defined as default for players to be able to join."
|
||||
end
|
||||
state = Keyword.put(state, :game, game)
|
||||
Amethyst.Server.Play.serve(client, state)
|
||||
end
|
||||
# Serverbound Plugin Message https://wiki.vg/Protocol#Serverbound_Plugin_Message_(configuration)
|
||||
|
188
apps/example_game/.credo.exs
Normal file
188
apps/example_game/.credo.exs
Normal file
@ -0,0 +1,188 @@
|
||||
%{
|
||||
#
|
||||
# You can have as many configs as you like in the `configs:` field.
|
||||
configs: [
|
||||
%{
|
||||
#
|
||||
# Run any config using `mix credo -C <name>`. If no config name is given
|
||||
# "default" is used.
|
||||
#
|
||||
name: "default",
|
||||
#
|
||||
# These are the files included in the analysis:
|
||||
files: %{
|
||||
#
|
||||
# You can give explicit globs or simply directories.
|
||||
# In the latter case `**/*.{ex,exs}` will be used.
|
||||
#
|
||||
included: [
|
||||
"lib/",
|
||||
"src/",
|
||||
"test/",
|
||||
],
|
||||
excluded: [~r"/_build/", ~r"/deps/"]
|
||||
},
|
||||
plugins: [],
|
||||
requires: [],
|
||||
strict: false,
|
||||
parse_timeout: 5000,
|
||||
color: true,
|
||||
#
|
||||
# You can customize the parameters of any check by adding a second element
|
||||
# to the tuple.
|
||||
#
|
||||
# To disable a check put `false` as second element:
|
||||
#
|
||||
# {Credo.Check.Design.DuplicatedCode, false}
|
||||
#
|
||||
checks: %{
|
||||
enabled: [
|
||||
#
|
||||
## Consistency Checks
|
||||
#
|
||||
{Credo.Check.Consistency.ExceptionNames, []},
|
||||
{Credo.Check.Consistency.LineEndings, []},
|
||||
{Credo.Check.Consistency.ParameterPatternMatching, []},
|
||||
{Credo.Check.Consistency.SpaceAroundOperators, []},
|
||||
{Credo.Check.Consistency.SpaceInParentheses, []},
|
||||
{Credo.Check.Consistency.TabsOrSpaces, []},
|
||||
|
||||
#
|
||||
## Design Checks
|
||||
#
|
||||
# You can customize the priority of any check
|
||||
# Priority values are: `low, normal, high, higher`
|
||||
#
|
||||
{Credo.Check.Design.AliasUsage,
|
||||
[priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]},
|
||||
{Credo.Check.Design.TagFIXME, []},
|
||||
# You can also customize the exit_status of each check.
|
||||
# If you don't want TODO comments to cause `mix credo` to fail, just
|
||||
# set this value to 0 (zero).
|
||||
#
|
||||
{Credo.Check.Design.TagTODO, [exit_status: 0]},
|
||||
|
||||
#
|
||||
## Readability Checks
|
||||
#
|
||||
{Credo.Check.Readability.AliasOrder, []},
|
||||
{Credo.Check.Readability.FunctionNames, []},
|
||||
{Credo.Check.Readability.LargeNumbers, []},
|
||||
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
|
||||
{Credo.Check.Readability.ModuleAttributeNames, []},
|
||||
{Credo.Check.Readability.ModuleDoc, []},
|
||||
{Credo.Check.Readability.ModuleNames, []},
|
||||
{Credo.Check.Readability.ParenthesesInCondition, []},
|
||||
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
|
||||
{Credo.Check.Readability.PipeIntoAnonymousFunctions, []},
|
||||
{Credo.Check.Readability.PredicateFunctionNames, []},
|
||||
{Credo.Check.Readability.PreferImplicitTry, []},
|
||||
{Credo.Check.Readability.RedundantBlankLines, []},
|
||||
{Credo.Check.Readability.Semicolons, []},
|
||||
{Credo.Check.Readability.SpaceAfterCommas, []},
|
||||
{Credo.Check.Readability.StringSigils, []},
|
||||
{Credo.Check.Readability.TrailingBlankLine, []},
|
||||
{Credo.Check.Readability.TrailingWhiteSpace, []},
|
||||
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
|
||||
{Credo.Check.Readability.VariableNames, []},
|
||||
{Credo.Check.Readability.WithSingleClause, []},
|
||||
|
||||
#
|
||||
## Refactoring Opportunities
|
||||
#
|
||||
{Credo.Check.Refactor.Apply, []},
|
||||
{Credo.Check.Refactor.CondStatements, []},
|
||||
{Credo.Check.Refactor.CyclomaticComplexity, []},
|
||||
{Credo.Check.Refactor.FilterCount, []},
|
||||
{Credo.Check.Refactor.FilterFilter, []},
|
||||
{Credo.Check.Refactor.FunctionArity, []},
|
||||
{Credo.Check.Refactor.LongQuoteBlocks, []},
|
||||
{Credo.Check.Refactor.MapJoin, []},
|
||||
{Credo.Check.Refactor.MatchInCondition, []},
|
||||
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
|
||||
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
|
||||
{Credo.Check.Refactor.Nesting, []},
|
||||
{Credo.Check.Refactor.RedundantWithClauseResult, []},
|
||||
{Credo.Check.Refactor.RejectReject, []},
|
||||
{Credo.Check.Refactor.UnlessWithElse, []},
|
||||
{Credo.Check.Refactor.WithClauses, []},
|
||||
|
||||
#
|
||||
## Warnings
|
||||
#
|
||||
{Credo.Check.Warning.ApplicationConfigInModuleAttribute, []},
|
||||
{Credo.Check.Warning.BoolOperationOnSameValues, []},
|
||||
{Credo.Check.Warning.Dbg, []},
|
||||
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
|
||||
{Credo.Check.Warning.IExPry, []},
|
||||
{Credo.Check.Warning.IoInspect, []},
|
||||
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []},
|
||||
{Credo.Check.Warning.OperationOnSameValues, []},
|
||||
{Credo.Check.Warning.OperationWithConstantResult, []},
|
||||
{Credo.Check.Warning.RaiseInsideRescue, []},
|
||||
{Credo.Check.Warning.SpecWithStruct, []},
|
||||
{Credo.Check.Warning.UnsafeExec, []},
|
||||
{Credo.Check.Warning.UnusedEnumOperation, []},
|
||||
{Credo.Check.Warning.UnusedFileOperation, []},
|
||||
{Credo.Check.Warning.UnusedKeywordOperation, []},
|
||||
{Credo.Check.Warning.UnusedListOperation, []},
|
||||
{Credo.Check.Warning.UnusedPathOperation, []},
|
||||
{Credo.Check.Warning.UnusedRegexOperation, []},
|
||||
{Credo.Check.Warning.UnusedStringOperation, []},
|
||||
{Credo.Check.Warning.UnusedTupleOperation, []},
|
||||
{Credo.Check.Warning.WrongTestFileExtension, []}
|
||||
],
|
||||
disabled: [
|
||||
#
|
||||
# Checks scheduled for next check update (opt-in for now)
|
||||
{Credo.Check.Refactor.UtcNowTruncate, []},
|
||||
|
||||
#
|
||||
# Controversial and experimental checks (opt-in, just move the check to `:enabled`
|
||||
# and be sure to use `mix credo --strict` to see low priority checks)
|
||||
#
|
||||
{Credo.Check.Consistency.MultiAliasImportRequireUse, []},
|
||||
{Credo.Check.Consistency.UnusedVariableNames, []},
|
||||
{Credo.Check.Design.DuplicatedCode, []},
|
||||
{Credo.Check.Design.SkipTestWithoutComment, []},
|
||||
{Credo.Check.Readability.AliasAs, []},
|
||||
{Credo.Check.Readability.BlockPipe, []},
|
||||
{Credo.Check.Readability.ImplTrue, []},
|
||||
{Credo.Check.Readability.MultiAlias, []},
|
||||
{Credo.Check.Readability.NestedFunctionCalls, []},
|
||||
{Credo.Check.Readability.OneArityFunctionInPipe, []},
|
||||
{Credo.Check.Readability.OnePipePerLine, []},
|
||||
{Credo.Check.Readability.SeparateAliasRequire, []},
|
||||
{Credo.Check.Readability.SingleFunctionToBlockPipe, []},
|
||||
{Credo.Check.Readability.SinglePipe, []},
|
||||
{Credo.Check.Readability.Specs, []},
|
||||
{Credo.Check.Readability.StrictModuleLayout, []},
|
||||
{Credo.Check.Readability.WithCustomTaggedTuple, []},
|
||||
{Credo.Check.Refactor.ABCSize, []},
|
||||
{Credo.Check.Refactor.AppendSingleItem, []},
|
||||
{Credo.Check.Refactor.DoubleBooleanNegation, []},
|
||||
{Credo.Check.Refactor.FilterReject, []},
|
||||
{Credo.Check.Refactor.IoPuts, []},
|
||||
{Credo.Check.Refactor.MapMap, []},
|
||||
{Credo.Check.Refactor.ModuleDependencies, []},
|
||||
{Credo.Check.Refactor.NegatedIsNil, []},
|
||||
{Credo.Check.Refactor.PassAsyncInTestCases, []},
|
||||
{Credo.Check.Refactor.PipeChainStart, []},
|
||||
{Credo.Check.Refactor.RejectFilter, []},
|
||||
{Credo.Check.Refactor.VariableRebinding, []},
|
||||
{Credo.Check.Warning.LazyLogging, []},
|
||||
{Credo.Check.Warning.LeakyEnvironment, []},
|
||||
{Credo.Check.Warning.MapGetUnsafePass, []},
|
||||
{Credo.Check.Warning.MixEnv, []},
|
||||
{Credo.Check.Warning.UnsafeToAtom, []}
|
||||
|
||||
# {Credo.Check.Refactor.MapInto, []},
|
||||
|
||||
#
|
||||
# Custom checks can be created using `mix credo.gen.check`.
|
||||
#
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
4
apps/example_game/.formatter.exs
Normal file
4
apps/example_game/.formatter.exs
Normal file
@ -0,0 +1,4 @@
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
26
apps/example_game/.gitignore
vendored
Normal file
26
apps/example_game/.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
example_game-*.tar
|
||||
|
||||
# Temporary files, for example, from tests.
|
||||
/tmp/
|
3
apps/example_game/README.md
Normal file
3
apps/example_game/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Example Game
|
||||
|
||||
This module is a simple game which is made to aid in designing and testing Amethyst's APIs.
|
19
apps/example_game/lib/example/application.ex
Normal file
19
apps/example_game/lib/example/application.ex
Normal file
@ -0,0 +1,19 @@
|
||||
defmodule Example.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
{Example.Game, {}}
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Example.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
end
|
8
apps/example_game/lib/example/game.ex
Normal file
8
apps/example_game/lib/example/game.ex
Normal file
@ -0,0 +1,8 @@
|
||||
defmodule Example.Game do
|
||||
use Amethyst.API.Game, meta: [default: true]
|
||||
|
||||
@impl true
|
||||
def instantiate() do
|
||||
{:ok, [:hello]}
|
||||
end
|
||||
end
|
32
apps/example_game/mix.exs
Normal file
32
apps/example_game/mix.exs
Normal file
@ -0,0 +1,32 @@
|
||||
defmodule Example.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :example_game,
|
||||
version: "0.1.0",
|
||||
build_path: "../../_build",
|
||||
config_path: "../../config/config.exs",
|
||||
deps_path: "../../deps",
|
||||
lockfile: "../../mix.lock",
|
||||
elixir: "~> 1.17",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger],
|
||||
mod: {Example.Application, []}
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:amethyst, in_umbrella: true}
|
||||
]
|
||||
end
|
||||
end
|
1
apps/example_game/test/test_helper.exs
Normal file
1
apps/example_game/test/test_helper.exs
Normal file
@ -0,0 +1 @@
|
||||
ExUnit.start()
|
Loading…
Reference in New Issue
Block a user