Hello, I chose to use Elixir as “scripting” language for my game, I am very new to Elixir but I know Erlang well.
My goal is to somewhat replicate LPC, which is an object oriented langage in C for LP Muds which used a lot back in the days.
So far I managed to come by borrowing examples from an OOP lib I found here: oop/oop.ex at master · wojtekmach/oop · GitHub
What I want to do is to define game objects, and let them “inherit” from a parent object, like this:
Ao.ShortSword -> Ao.Weapon -> Ao.Object
This is done by defining an object macro that lets me inherit from one other object with the ::
expression, for example Ao.Weapon :: Ao.Object
If the object macro detects the precense of an inherited object, it will create delegates for all methods in the parent and delegate from the object to the parent.
So far everything works like a charm, but now I need to add “properties” to the objects. A property is a key,value that describes the object some how, for example:
set_property(:colors, [{:red, 80, :yellow, 20}])
set_property(:materials: [{:bronze, 100}])
set_property(:mesh, <<"BronzeShortSword.fbx">>)
The issue I am trying to solve now that not all properties are equal, they are divided into shallow and deep properties. Shallow properties are things that I expect
to be queried more than 99.99% of the times, in most cases this would be properties like:
:position
:rotation
:mesh
These would be saved in a mnesia table for quick access.
And then all other properties that will only be queried or written in rare cases are saved in a long term (slower) database, such as Riak.
To define this I thought I could use Elixirs built-in attribute system, for example:
object Ao.Weapon :: Ao.Object do
@shallow :position
@shallow :rotation
@shallow :mesh
The problem I face is that the Ao.Object.set_property needs to know the objects (Ao.Weapon) shallow attributes so it can store the value in the mnesia DB
instead of Riak, but can not access it because they are stored in Ao.Weapon.
Right now I am considering defining the set_property function in a macro when creating the module using the object macro, but to be honest, I have literally no idea what is a good approach for this.
I hope that someone can guide me towards a good solution.
Here is the code I have so far, please note at Code.eval_quoted(method, [], __ENV__)
I add the delegate, maybe a good solution would be to pass the module Ao.Weapon
to the Ao.Object.set_property
there:
defmodule Ao do
defmacro object(obj_expr, block, _opts \\ []) do
{object, parent} = case obj_expr do
{:"::", _, [obj, parent]} ->
{obj, parent}
obj ->
{obj, nil}
end
AoLibObj.Builder.build(object, parent, block)
end
end
defmodule AoLibObj.Builder do
def build(object, parent, block) do
quote do
defmodule unquote(object) do
parent_obj = unquote(parent)
Module.register_attribute(__MODULE__, :parent, accumulate: false)
Module.put_attribute(__MODULE__, :parent, parent_obj)
Module.register_attribute(__MODULE__, :shallow, accumulate: true)
unquote(block)
def methods do
built_ins = [
object: 0, methods: 0, clone: 1
]
__MODULE__.__info__(:functions) -- built_ins
end
if parent_obj != nil do
for {fun, arity} <- parent_obj.methods() do
method = AoLibObj.Builder.inherit_method(fun, arity, parent_obj)
Code.eval_quoted(method, [], __ENV__)
IO.inspect(fun)
end
end
def clone(uid \\ nil) do
uid = if uid == nil do AoLibObj.Builder.make_uid() else uid end
%{
:uid => uid,
:object => unquote(object),
}
end
end
end
end
def inherit_method(method, arity, parent_obj) do
args = (0..arity) |> Enum.drop(1) |> Enum.map(fn i -> {:"arg#{i}", [], Ao} end)
IO.inspect(args)
{:defdelegate, [context: Ao, import: Kernel],
[{method, [], args}, [to: parent_obj]]}
end
def make_uid() do
<<a::32, b::16, c::16, d::16, e::48>> = :crypto.strong_rand_bytes(16)
str =
:io_lib.format(
"~8.16.0b-~4.16.0b-4~3.16.0b-~4.16.0b-~12.16.0b",
[a, b, Bitwise.band(4095, c), Bitwise.band(16383, d), Bitwise.bor(32768, e)]
)
:erlang.list_to_binary(str)
end
end
import Ao
object Ao.Object do
def init() do
IO.puts("Instanced Ao.Object!")
end
def set_property(_key, _value, child \\ nil ) do
IO.puts("BASE!!! Ao.Object")
end
end
import Ao
object Ao.Weapon :: Ao.Object do
@shallow :position
@shallow :rotation
@shallow :mesh
end