You're moving in a direction that can be a siren song. You basically want to 
add what I'd call 'traits' to actor dynamically. Now, this is not so difficult 
to do. In Smalltalk and Javascript this is quite easy to do because you can 
dynamically extend the fields of an instance. In Java, everything must 
statically compile. The advantage is of course that then everything is 
statically compiled and the compiler can help you tremendously to write 
error-free code.

If you implement the trait model in a type safe way, it tends to become a lot 
harder to write code for it. That is why it is very easy to implement such a 
model in languages like Smalltalk, Javascript, and I recall Python. You just 
add extra fields and methods. Since nothing is type checked in the compiler, 
you do not have to provide any metadata that makes the compiler happy. However, 
if you want to do this
type safe in Java then the APIs become a lot more ugly. And if you're willing 
to give up type safety, the API is a lot harder to use than plain Java because 
you need to use some kind of property model.

That said, there is a trick I am very fond of. Actually it is two tricks. 
First, instead of focusing how to implement something it often helps to view it 
from the perspective how to use something. As we're all implementers, we tend 
to want to provide it all to our customers, as easy as possible. However, we 
often forget that the client just has more knowledge we as providers have. A 
given client of these
traits knows exactly which traits are needed. For type safety, that client muse 
depend on the actual trait types. Something that is impossible for the 
provider, the whole idea of the trait provider is to be oblivious of the actual 
traits. Unlike the client ...

The second trick is the magic of interface proxies. It is already 6 years ago I 
came up with the idea to use the annotation interfaces as a front for the OSGi 
configuration data. I still love it every day I program a component. I've used 
this trick in lots of places. It allows you to write code almost as simple as 
in Python, Javascript, or Smalltalk but you get much more type safety.

So if we look from the perspective of the provider we do actually have normal 
Java type dependencies. A client that uses Entity and Position knows exactly 
that it uses these types and it will have import packages to prove it. Since it 
has type dependencies, OSGi tends to be invisible. If your code runs with these 
dependencies all things work ok. This is very important in a dynamic system.

So lets say we have a Trait Manager service. How would a client that has Entity 
& Position wants to work?

        interface Position { int x(); int y(); }
        interface Entity { String id(); }

So if the client wants to use both at the same time, they can create a new 
interface:

        interface ClientView extends Position, Entity {}

This new interfaces is your binding, it binds whatever types the client feels 
like. So assume we have a Trait Manager service:

        interface TraitManager {
                <T> T create( Class<T> type )
        }

The Trait Manager returns a proxy to an underlying host object. The proxy 
implements all the interfaces from the given type. Totally type safe and easy 
to use. 


        void client() {
                ClientView cv = tm.create(ClientView.class);
                int cv.x();
                String id = cv.id();
        }


How to implement this? Well, the proxy must dispatch the method calls to an 
object that knows the trait type, either Position or Entity. We want those 
types to come from a Trait service.

        interface Trait {
                boolean handles(Class<?> c);
                void augment(TraitHost host);
        }

The create method of the Trait Manager service could look like this:

        public <T> T create(Class<T> view) {
                TraitHostImpl host = new TraitHostImpl();

                for (Class<?> c : view.getInterfaces()) {
                        findTrait(c).augment(host);
                }
                return view.cast(Proxy.newProxyInstance(view.getClassLoader(), 
new Class[] {
                        view
                }, host));
        }

The host object is a hidden object that contains the mapping from class -> 
instance. It acts as the Invocation Handler for the proxy
but it needs a public interface because the Trait services must be able to 
_add_ their trait to it. 

        interface TraitHost {
                <T> void addInstance(Class<T> c, T instance);
        }


So how would this look like if you want to provide a trait in a separate Bundle?

        interface Entity { String id(); }

        class EntityTrait implements TraitManager.Trait { 
                @Override public void augment(TraitHost host) { 
                        String id = UUID.randomUUID() .toString(); 
                        host.addInstance(Entity.class, () -> id); 
                } 
                @Override public boolean handles(Class<?> c) { return c == 
Entity.class; } 
        }

This is of course a sketch. A major problem with these kind of solutions is the 
dynamicity of OSGi. The solution here is I think quite ok if you make sure the 
trait's type is exported by the same bundle that provides the Trait service for 
that type and there are no other exporters. The type dependency will then 
ensure that you can never create a view  where the corresponding Trait is 
missing. This can of course also be achieved with explicit capabilities.

However, the problem gets more problematic when you start to share your objects 
with parties that want to augment the trait host further. I.e. I get a Position 
object in my code but now I want to add a Foo trait if it is not already there:

        Foo f = tm.ensure( Foo.class, cv );

This imho inevitable extension will create multiple proxies on the same 
underlying trait host. With one proxy, the trait host will get garbage 
collected if the client goes away. However, when you have multiple proxies the 
trait host will not be garbage collected until _all_ proxies become 
unreachable. This means that in some cases the trait host is holding on to 
classes that should be garbage collected. This is solvable but to much for this 
mail.

Anyway, some caution is in place. When I was first hired by Ericsson in 1992 I 
developed a fantastic framework to handle data in a network management system 
with some of the qualities you're looking. I really think it worked rather well 
but developers hated it because it was always something extra over normal code. 
I do think the proxy trick is quite powerful and works very nice but almost 
nothing beats plain old java code. So be aware. The rules that this model will 
oblige your developers to follow will not always be appreciated. And of course 
as the GC showed, there are lots of corner cases to be aware of.

I've enclosed the files I used to verify that I did not make compiler errors 
... which I probably did anyway. You can try them out. This is of course an 
extremely minimal implementation.

Hope this helps. Kind regards,

        Peter Kriens



Attachment: TraitManager.java
Description: Binary data

Attachment: TraitManagerTest.java
Description: Binary data


> On 17 Sep 2020, at 03:37, Zyle Moore via osgi-dev <osgi-dev@mail.osgi.org> 
> wrote:
> 
> In my game, all content, mechanics, and systems will be modular, and
> based on OSGi bundles. One of these bundles is for an Entity. Entities
> are uniquely identifiable things in the game world. Examples would
> include trees, rocks, and characters. The game though does not
> necessarily have to have any of those. If the game doesn't need trees,
> it shouldn't have trees. If it doesn't need rocks, it shouldn't have
> rocks. The Entity is the unique identity other things are associated
> with. One of the things an Entity can be associated with is a Position.
> A Position is an (x, y) coordinate in the world. Positions are in a
> separate bundle than Entities. Positions are not inherently tied to
> Entities, and Entities do not inherently have Positions, even though
> they will likely be used together, depending on the game.
> 
> At this point, there are two bundles; Entity, and Position. To get more
> functionality out of these two, a third bundle is needed;
> EntityPosition, which provides a glue class (EntityPosition) which
> associates a Position with an Entity.
> 
> ```
> +------------+    +--------------------+     +----------+
> |            |    |                    |     |          |
> | Entity     |    | EntityPosition     |     | Position |
> |            |    |                    |     |          |
> +------------+    +--------------------+     +----------+
> |            |    |                    |     |          |
> | id: String +--->+ entity: Entity     |     | x: Int   |
> |            |    | position: Position +<----+ y: Int   |
> +------------+    |                    |     |          |
>                   +--------------------+     +----------+
> ```
> 
> In this simple example, two bundles became three, in order to associate
> them. If I only needed to turn two into three, that wouldn't be so bad.
> But if I wanted to associate a name with an Entity, that adds another
> bundle. If I wanted to associate a sprite with an Entity, that adds two
> other bundles, one for the sprite, one for the EntitySprite association.
> This results in an explosion of small, but simple, bundles; each one
> only binding two things from other bundles together.
> 
> - Entity
> - Position
>  - EntityPosition
> - Name
>  - EntityName
> - Sprite
>  - EntitySprite
> - Sound
>  - EntitySound
> 
> This has always seemed off to me somehow. Even though the code is small,
> easy, and sharing a common pattern, the sheer scale seems overwhelming.
> Each one has to be built, tested, deployed, and referenced; all for the
> sole purpose of binding two things together.
> 
> In order to try to avoid this explosion of bundles, a possible solution
> came to me. Each service registered in the framework service registry
> can have properties associated with it. Could I use these properties to
> provide the relationship between the services? In this example, we
> would have one Entity component configuration, one Position component
> configuration, and one of the properties of the Position configuration
> would reference the Entity that the Position belongs to.
> 
> ```
> entity.uuid.1:
>     entity.id: tree
> 
> position.uuid.1:
>     position.for: entity.uuid.1
>     position.x: 0
>     position.y: 0
> ```
> 
> My thinking is that the service properties can provide the glue, or
> associations, between components/services, reducing the need for a
> separate glue class. Additionally, if a Position needed to be associated
> with something other than an Entity, I still don't need to create a
> separate bundle, I can just change the `position.for` property to point
> to whatever the new thing is, assuming the service can be filtered. This
> way, the Position and Entity classes remain ignorant of the binding, and
> can be focused on only its x and y values, and if I ever need to know
> the Position of something, I can simply filter the Position services on
> the `position.for` property.
> _______________________________________________
> OSGi Developer Mail List
> osgi-dev@mail.osgi.org
> https://mail.osgi.org/mailman/listinfo/osgi-dev

_______________________________________________
OSGi Developer Mail List
osgi-dev@mail.osgi.org
https://mail.osgi.org/mailman/listinfo/osgi-dev

Reply via email to