I want to create an enity system with some special features based on Scala traits.
The basic idea is this: all components are traits that inherit from a common trait:
trait Component trait ComponentA extends Component
sometimes, in the case of a more complex hierarchy and interdependent components, it may look something like this:
trait ComponentN extends ComponentM { self: ComponentX with ComponentY => var a = 1 var b = "hello" }
etc. I came to the conclusion that the data related to each component should be contained on their own, and not in any storage inside Entity or elsewhere due to the speed of access. As a side note, this is why everything is changeable, so there is no need to think about immutability.
Entities are then created, mixing in features:
class Entity class EntityANXY extends ComponentA with ComponentN with ComponentX with ComponentY
Everything is in order here, however, I have a special requirement that I do not know how to execute the code. The requirement is as follows:
Each trait should provide an encoding method (?) That facilitates the collection of data related to traits in a universal form, for example, in the form of JSON or Map , like Map("a" -> "1", "b" -> "hello") , and the decoding method for translating such a mapping, if received, back to the variables associated with it. Also: 1) all encoding and decoding methods of all mixed signs are called in a bunch in an arbitrary manner using the Entity encode and decode(Map) methods and 2) should be available for calling separately specifying the type of attribute or, better, a string parameter, for example decode("component-n", Map) .
It is not possible to use methods with the same name that will be lost due to shadowing or overriding. I can think of a solution where all methods are stored in Map[String, Map[String, String] => Unit] for decoding and Map[String, () => Map[String, String]] for encoding in each object. This will work - a name will probably be available, as well as a call. However, this will lead to the preservation of the same information in each entity, which is unacceptable.
It is also possible to save these cards in a companion object so that it is not duplicated anywhere and decode encode and decode with an additional parameter that indicates a specific instance of the object.
The requirement may seem strange, but it is necessary because of the required speed and modularity. All of these solutions are clumsy, and I think Scala has a better and idiomatic solution, or maybe I missed some important architectural pattern here. So, is there a simpler and more idiomatic approach than the one associated with the companion object?
EDIT: I think that aggregation instead of inheritance could probably solve these problems, but at the cost of being unable to invoke methods directly on entities.
UPDATE: While exploring the pretty promising way suggested by Rex Kerr, I came across something that gets in the way. Here is a test case:
trait Component { def encode: Map[String, String] def decode(m: Map[String, String]) } abstract class Entity extends Component // so as to enforce the two methods trait ComponentA extends Component { var a = 10 def encode: Map[String, String] = Map("a" -> a.toString) def decode(m: Map[String, String]) { println("ComponentA: decode " + m) m.get("a").collect{case aa => a = aa.toInt} } } trait ComponentB extends ComponentA { var b = 100 override def encode: Map[String, String] = super.encode + ("b" -> b.toString) override def decode (m: Map[String, String]) { println("ComponentB: decoding " + m) super.decode(m) m.get("b").foreach{bb => b = bb.toInt} } } trait ComponentC extends Component { var c = "hey!" def encode: Map[String, String] = Map("c" -> c) def decode(m: Map[String, String]) { println("ComponentC: decode " + m) m.get("c").collect{case cc => c = cc} } } trait ComponentD extends ComponentB with ComponentC { var d = 11.6f override def encode: Map[String, String] = super.encode + ("d" -> d.toString) override def decode(m: Map[String, String]) { println("ComponentD: decode " + m) super.decode(m) m.get("d").collect{case dd => d = dd.toFloat} } }
and finally
class EntityA extends ComponentA with ComponentB with ComponentC with ComponentD
so that
object Main { def main(args: Array[String]) { val ea = new EntityA val map = Map("a" -> "1", "b" -> "3", "c" -> "what?", "d" -> "11.24") println("BEFORE: " + ea.encode) ea.decode(map) println("AFTER: " + ea.encode) } }
which gives:
BEFORE: Map(c -> hey!, d -> 11.6) ComponentD: decode Map(a -> 1, b -> 3, c -> what?, d -> 11.24) ComponentC: decode Map(a -> 1, b -> 3, c -> what?, d -> 11.24) AFTER: Map(c -> what?, d -> 11.24)
Components A and B are not affected by being clipped by inheritance resolution. Thus, this approach is applicable only in certain cases of the hierarchy. In this case, we see that ComponentD obscures everything else. Any comments are welcome.
UPDATE 2: I am posting a comment that answers this issue here for a better reference: "Scala linearizes all the traits. There must be a superwrite of everything that ends the chain. Your case, this means that C and A should still call super , and Component is the one that ends the chain with no-op. " - Rex Kerr