flodihn

flodihn

Advice how to implement my object system

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

Most Liked

stevensonmt

stevensonmt

I hope someone with more experience chimes in, but I think it is important to recognize that trying to shoe-horn object oriented paradigms like inheritance is destined to fall short in Elixir. Inheritance just is not part of the language by design.

I would recommend you define behaviours for your “object classes” and then have each specific module implement the behaviours it requires. So a sword can implement weapon behaviour, and a shovel can implement weapon behaviour and tool behaviour and so on.

kokolegorille

kokolegorille

You could use luerl, lua on the BEAM.

There is a spaceship game demo by @rvirding and videos on YT about luerl.

andyleclair

andyleclair

You should actually watch the youtube video linked in the OOP repo.

It is not serious, and you should not take it as a good example.

If you want an Entity Component System, you may want to try GitHub - ecsx-framework/ECSx: An Entity-Component-System framework for Elixir

Where Next?

Popular in Questions Top

Werner
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”: 14:57:30.512 [warn] ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
Kagamiiiii
Student &amp; New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
sergio
I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into t...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New

We're in Beta

About us Mission Statement