junkdog / artemis-odb

A continuation of the popular Artemis ECS framework
BSD 2-Clause "Simplified" License
776 stars 111 forks source link

GWT Compatibility #56

Closed Flet closed 10 years ago

Flet commented 10 years ago

I'm progressing nicely in the spaceship warrior rewrite/adaptation with artemis-odb and the libgdx maven archetype. Desktop and Android builds area running great!

However, I've run into an issues when packaging an HTML build via GWT in libgdx. In GWT, only a subset of Java is supported, and a few items are popping up as errors when attempting to compile.

A few easy ones can be remedied without a ton of effort:

However, the tough one is Reflection and using annotations. Specifically the @Mapper annotation. I think its possible to make this work, but it would take a good amount of effort.

@junkdog - Is GWT support something you feel is important to include in this framework?

Also, I found @SenkChristian's repo SenkChristian/artemis-gwt which deals with GWT in artemis, but the code supporting @Mapper is removed because it is tough to support (I assume). Also its an old/fork of artemis that is not artemis-odb. The solutions for the "easy fixes" above are being used here, but its built using gradle. Maybe efforts can be combined? :)

junkdog commented 10 years ago

Meh, damned be GWT... I've never played around with it myself, but it seems unnecessary to omit it.

The @Mapper associated reflection could be replaced by bytecode injection, but it would make project setup more restricted (meaning dependent on ant or maven for post-compile processing). Then again, if one is already using maven - agrotera's annotations already do just that, only more versatile and powerful.

If nothing else, I could build a separate GWT variant of artemis-odb. I'll make sure it makes it into the next release.

junkdog commented 10 years ago

Ofc: agrotera doesn't play with intellij (and, because I haven't tested it, presumably also netbeans).

csenk commented 10 years ago

Got a notification about this issue here. I would happily volunteer if you need any help in making it GWT compatible.

@Mapper annotation doesn't work because Reflection is not supported in GWT. This is the same for any bytecode injection. Always keep in mind that the result of a GWT compilation is pure JavaScript. Am 03.02.2014 20:41 schrieb "Adrian Papari" notifications@github.com:

Ofc: agrotera doesn't play with intellij (and, because I haven't tested it, presumably also netbeans).

Reply to this email directly or view it on GitHubhttps://github.com/junkdog/artemis-odb/issues/56#issuecomment-33991572 .

junkdog commented 10 years ago

Ouch, I thought it converted bytecode - not java source - into javascript. What an unfortunate implementation detail. Hmm. I guess it could be overcome with some clever (clever meaning ugly) rewriting prior to javascript transpilation kicks in.

As far as I'm aware only libgdx supports compiling to GWT, or are there other frameworks out there? Libgdx has a way about them when it comes to devising clever hacks. Maybe they already do something similar (their embedding of c++ inside java sources come to mind).

Help is always welcome ;) My schedule is pretty full atm, but I really want to release the next version as soon as possible, before the issue/feature list grows too big. Hopefully sometime before March.

I'll need to look into how to build variants with maven, but I think it's the best/easiest solution for quick results: supply a different, API-compatible build of artemis-odb with GWT support. Possibly omitting stuff which is not strictly necessary.

How much did you have to cut out to make artemis fly with GWT?

csenk commented 10 years ago

Basically you have to cut all reflection or bytecode related stuff, second you have to cut out classes/methods NOT listed here: http://www.gwtproject.org/doc/latest/RefJreEmulation.html Although you can provide your own implementation for not yet emulated classes (like I did with BitSet).

It can be done pretty quick :)

2014-02-03 Adrian Papari notifications@github.com:

Ouch, I thought it converted bytecode - not java source - into javascript. What an unfortunate implementation detail. Hmm. I guess it could be overcome with some clever (clever meaning ugly) rewriting prior to javascript transpilation kicks in.

As far as I'm aware only libgdx supports compiling to GWT, or are there other frameworks out there? Libgdx has a way about them when it comes to devising clever hacks. Maybe they already do something similar (their embedding of c++ inside java sources come to mind).

Help is always welcome ;) My schedule is pretty full atm, but I really want to release the next version as soon as possible, before the issue/feature list grows too big. Hopefully sometime before March.

I'll need to look into how to build variants with maven, but I think it's the best/easiest solution for quick results: supply a different, API-compatible build of artemis-odb with GWT support. Possibly omitting stuff which is not strictly necessary.

How much did you have to cut out to make artemis fly with GWT?

Reply to this email directly or view it on GitHubhttps://github.com/junkdog/artemis-odb/issues/56#issuecomment-33997057 .

Flet commented 10 years ago

Ah, hello there @SenkChristian! :) editing my response now as I see you just posted :)

It also may be possible to separate the code dealing with @Mapper and designate it as "server only". In either case, this means the @Mapper annotation could not be used if GWT is a build target for your application... which is a bit unfortunate...

The libgdx folks went through similar pains with reflection in prepping for GWT: http://www.badlogicgames.com/wordpress/?p=2764

In the end, they built a wrapper around Java's Reflection API: https://github.com/libgdx/libgdx/wiki/Reflection

I would look to their pattern as a way to solve for this in artemis-odb :).

...I wonder if someone else has built this wrapper as a standalone library... It feels like it could be a common problem for a lot of GWT projects.

Flet commented 10 years ago

Also, I don't think building variants with maven would be necessary. Instead, a gwt.xml file can be included (along with the emulated "super" classes for GWT only) and things would Just Work for all targets using the same jar.

junkdog commented 10 years ago

Hmm, might have to look into libgdx's reflection API. Or skip the mapper reflection initially, if it turns out to be more trouble than it's worth.

Just so I'm clear, the mere presence of annotations doesn't cause GWT to break? I'm thinking about @PooledWeaver and @PackedWeaver primarily.- since if the components aren't woven into their targeted type, they're still valid old/pojo-style components.

Also, I don't think building variants with maven would be necessary. Instead, a gwt.xml file can be included (along with the emulated "super" classes for GWT only) and things would Just Work for all targets using the same jar.

Even better ;)

csenk commented 10 years ago

The problem with making a library compatible with GWT within the same artifact is that you have to package the sources and GWT module descriptor with them. Some may not want this to be included in the same jar.

PlayN and some other libraries provide the .gwt.xml in the -sources.jar and it turned out very well at least for me since you can declare a dependency with the appropriate classifier. The approach from LibGDX is well engineered but they introduce a reflection abstraction which you may not want to use since it introduces another layer of complexity.

My suggestion: Go the same way as for example guava and their distribution guava-gwt. They make heavy use of emulation to support most of the guava API in GWT as well the annotation @GwtCompatible which they use to identify the sources that should be emulated.

2014-02-03 Dan Flettre notifications@github.com:

Also, I don't think building variants with maven would be necessary. Instead, a gwt.xml file can be included (along with the emulated "super" classes for GWT only) and things would Just Work for all targets using the same jar.

Reply to this email directly or view it on GitHubhttps://github.com/junkdog/artemis-odb/issues/56#issuecomment-34003139 .

junkdog commented 10 years ago

Thanks, I'll look into GWT. Took me a while to realize why packaging the gwt.xml file inside the main artifact was a bad idea...

apotapov commented 10 years ago

I'm curious what it would take to make this project GWT compatible. I tried to do this with gdx-artemis. I've already removed all the reflection and annotations and added pooling and libgdx data structures to replace artemis / java naitive ones. But I never got around to actually testing it with GWT. Anyway, y'all are welcome to take a look.

Flet commented 10 years ago

Right, same jar is a bad idea. I realize this too now :). Child module is the way to go.

I did some work yesterday looking to see if the libgdx reflect wrapper/abstraction would work and things look promising, but still digging in.

Essentially, its replacing the reflect stuff in ComponentMapperInitHelper.config with the libgdx reflect classes.

For example: field.getGenericType() becomes field.getElementType(0) (from the Field.java in com.badlogic.gdx.utils.reflect)

All the reflect classes essentially passthrough to the actual reflect calls, so the impact is virtually zero for Desktop/Android.

Next, a new maven child module artemis-gwt is created. This module would be added as a maven dependency only if the developer's project is compiling to GWT. It holds the the GWT magic similar to what is in the gdx-backend-gwt gwrtref package. It does two things: replaces the reflect classes with GWT compatible versions and also has a "generation" class that builds the set of classes that will be reflected. (per the config below).

Finally, in the developer's project, add the special configuration property in the .gwt file to include all the classes/package that could get mapped via the annotation. In a libgdx maven project, this would be the html5 module.

So, its looking possible, but still some work to do. I'll continue down the path and push a branch to my fork if I see any promise (and so you all can scrutinize it!).

@SenkChristian I'll check out what gwt-guava is doing tonight as well to see if it makes sense. I like libgdx's approach even with the additonal complexity/abstraction, but certainly good to see what others are doing.

Flet commented 10 years ago

@apotapov cool! I will take a look at what you did for sure... at the same time I want to see if its possible to keep the @Mapper annotation :)

csenk commented 10 years ago

You can maybe somehow utilize the GWT code generator mechanism to make use of the @Mapper annotation. But I'm not sure if you can make it happen to work on the GWT side exactly like on the java side.

My suggestion would be something like the following:

public class MyEntityProcessingSystem extends EntityProcessingsystem {

public interface MyMapper extends Mapper {

Position getPosition(Entity e); Velocity getVelocity(Entity e); } private final MyMapper mapper = GWT.create(MyMapper.class); //... }

That would be the perfect entry point for GWT code generation to kick in.

2014-02-04 Dan Flettre notifications@github.com:

@apotapov https://github.com/apotapov cool! I will take a look at what you did for sure... at the same time I want to see if its possible to keep the @Mapper annotation :)

Reply to this email directly or view it on GitHubhttps://github.com/junkdog/artemis-odb/issues/56#issuecomment-34089976 .

apotapov commented 10 years ago

This sounds like a whole lot of extra work to preserve functionality that in my opinion has very little necessity.

Firstly, you can do ComponentMapper injection without @Mapper annotation. When I was looking at the injection code. The first thing I did was to remove the dependency on @Mapper annotation. The injection code already checked for an instance of ComponentMapper field, so the annotation check was redundant.

Secondly, automatically injecting ComponentMappers actually created some roadblocks for me. I could not have one EntitySystem extend another. Automatic injection would not look at the parent classes and inject the mappers there as well. (so currently you cannot create a hierarchy of Systems) Also, if I ever wanted to use a ComponentMapper in a utility class that was not a system, it was impossible because ComponentMapper instantiation was limited to injection. There are ways around that, but they are cumbersome.

So I decided to do away with automagic ComponentMapper injection in gdx-artemis and I never looked back. The library has become a lot more straight forward and flexible.

The additional benefit of getting rid of annotations and reflection (besides speed increase) is of course you don't have to jump hoops to get artemis to play nicely with GWT.

In any case, I'm curious to see how you guys end up solving this problem, but in my opinion the easiest solution was to get rid of @Mapper and component injection. It's much cleaner, easier to understand for the library's user, extensible and plays nicer with GWT. Even if the price is a little more boiler plate code, seems like a no brainer to me.

csenk commented 10 years ago

I removed @Mapper in my artemis GWT adaption as well.

2014-02-04 Andrew Potapov notifications@github.com:

This sounds like a whole lot of extra work to preserve functionality that in my opinion has very little necessity.

Firstly, you can do ComponentMapper injection without @Mapperhttps://github.com/Mapperannotation. When I was looking at the injection code. The first thing I did was to remove the dependency on @Mapper https://github.com/Mapperannotation. The injection code already checked for an instance of ComponentMapper field, so the annotation check was redundant.

Secondly, automatically injecting ComponentMappers actually created some roadblocks for me. I could not have one EntitySystem extend another. Automatic injection would not look at the parent classes and inject the mappers there as well. (so currently you cannot create a hierarchy of Systems) Also, if I ever wanted to use a ComponentMapper in a utility class that was not a system, it was impossible because ComponentMapper instantiation was limited to injection. There are ways around that, but they are cumbersome.

So I decided to do away with automagic ComponentMapper injection in gdx-artemis and I never looked back. The library has become a lot more straight forward and flexible.

The additional benefit of getting rid of annotations and reflection (besides speed increase) is of course you don't have to jump hoops to get artemis to play nicely with GWT.

In any case, I'm curious to see how you guys end up solving this problem, but in my opinion the easiest solution was to get rid of @Mapperhttps://github.com/Mapperand component injection. It's much cleaner, easier to understand for the library's user, extensible and plays nicer with GWT. Even if the price is a little more boiler plate code, seems like a no brainer to me.

Reply to this email directly or view it on GitHubhttps://github.com/junkdog/artemis-odb/issues/56#issuecomment-34094010 .

Flet commented 10 years ago

Thank you both for sharing your experiences, its super informative!

Yeah, you're both right, @Mapper is not really needed... could instead rely on types instead. The explicitness of this is a matter of style I suppose.

Both right as well regarding cutting the magic out: Its not a huge sacrifice. I could totally see this being the end game if this reflection approach gets to be really gross.

Automatic injection would not look at the parent classes

This was true even in straight Java? interesting... this is probaby because its using getDeclaredFields() which only returns fields for the current class and not parent classes. Additonal code would be needed to walk up the class hierarchy.

Looking forward to digging in to this more in 5-6 hours or so.

apotapov commented 10 years ago

I don't remember exactly if it was an issue with inheritance, but one thing I couldn't do for sure is something like:

public abstract class ParentSystem<T extends Component> extends EntitySystem {
    ComponentMapper<T> mapper;
}

Reflection would throw up because it could not figure out what type the mapper was. (hint: runtime type erasure) Might not be a common use case, but it was needed for me.

I also created a couple of managers that use ComponentMappers. (impossible with injection)

Specifically: https://github.com/apotapov/gdx-artemis/blob/master/src/main/java/com/artemis/managers/SingletonComponentManager.java

I find this manager useful in a many systems for accessing things like the game map/board, the hud, and a few other singleton entities/components.

denniskaselow commented 10 years ago

In my Dart port of Artemis, I don't use annotations for injection because I thought of them to being redundant. Just declaring a ComponentMapper is enough. Instead I am offering 2 different libraries so the user can decide whether he wants to use injection or not (because reflection (Mirrors in Dart) increases the code size of the generated JavaScript). The only difference between the libraries is, that I extend the non-injecting World and just override my initializeManager and initializeSystem-functions. https://github.com/denniskaselow/dartemis/blob/master/lib/src/mirrors/world.dart

Might be a feasible approach too, if injection is too much of a hassle with GWT.

As a bonus I'm also injecting Managers and Systems, not only ComponentMappers (upwards the type hierarchy, wasn't really aware it's not done in Artemis, just thought I forgot it when porting).

But I also like the idea of hooking into the GWT-compile, because I am thinking about something similar for dartemis. If there is a ComponentMapper, EntitySystem or Manager declared, add the appropriate code to the initialize-method before the code is compiled to JavaScipt.

Also, interesting discussion, wasn't aware of all these Artemis forks. Have to take a look at them hunting for great ideas ;).

junkdog commented 10 years ago

Morning,

Although I don't use @Mapper myself, I'm not too keen on breaking compatibility with older versions. For GC reasons and keeping lower-end android devices happy, I prefer keeping reflection-based injection implicit. The scenario that one wishes to do something funky with the component mappers also exist - like an extended ComponentMapper - which may or may not break the code.

Reflection would throw up because it could not figure out what type the mapper was. (hint: runtime type erasure) Might not be a common use case, but it was needed for me.

Huh, are you sure type erasure kicks in? I thought type information would be preserved, but I've been known to be confused by type erasure before. Must reify my type erasure knowledge.

I also created a couple of managers that use ComponentMappers. (impossible with injection)

@Mappers works for both entity systems and managers in artemis-odb.

PS. In your singleton manager, you can do identity checks with containsKey since classes are instantiated once.

Flet commented 10 years ago

@junkdog by "keeping reflection-based injection implicit", do you mean dump the @Mapper annotation and instead always inject for ComponentMapper fields in Systems and Managers?

junkdog commented 10 years ago

Oops, i meant to write explicit: so no injection takes place unless the annotation is present, for the reasons stated (potential custom mappers etc). On Feb 6, 2014 12:27 AM, "Dan Flettre" notifications@github.com wrote:

@junkdog https://github.com/junkdog by "keeping reflection-based injection implicit", do you mean dump the @Mapper annotation and instead always inject for ComponentMapper fields in Systems and Managers?

Reply to this email directly or view it on GitHubhttps://github.com/junkdog/artemis-odb/issues/56#issuecomment-34274842 .

apotapov commented 10 years ago

Hmm, yeah the solution might be to only inject if the annotation is present and also provide a way to instantiate mappers manually. Does GWT blow up at compilation time if it sees annotations and reflection or is it only an issue at run time? If it's runtime, then this might be a relatively unintrusive solution.

lopho commented 10 years ago

Mappers can already be instantiated manually via these methods:

world.getMapper(Class<T> type)

which uses the following call

ComponentMapper.getFor(Class<T> type, World world);

or you could get one by constructor

BasicComponentMapper(Class<A> type, World world)

which has package visibility atm though, but its being called by ComponentMapper.getFor()

so as long as the annotation isn't used, these should not be a problem. Can't speak for the PackedComponents, as I haven't had much to do with them yet.

junkdog commented 10 years ago

Does GWT blow up at compilation time if it sees annotations and reflection or is it only an issue at run time?

Unless the java.lang annotations (@Override, @SuppressWarnings) are handled in a specialized manner, I doubt it would cause the compiler to bail out. I'm thinking that copying libgdx's reflection code might be the way to go though - since @Mapper would break compilation otherwise.

@PackedWeaver and @PooledWeaver can be safely ignored, since they'd act as normal components if not woven.

csenk commented 10 years ago

Annotations are perfectly OK with GWT as long as the compiler can see the annotations source (@Override and @SuppressWarnings are a part of the GWT JRE emulation). The problem with @Mapper is that it is evaluated with reflection which is not supported in GWT, that is the only reason why the behaviour around @Mapper is not supported, while the annotations itself is supported,

2014-02-07 Adrian Papari notifications@github.com:

Does GWT blow up at compilation time if it sees annotations and reflection or is it only an issue at run time?

Unless the java.lang annotations (@Override, @SuppressWarnings) are handled in a specialized manner, I doubt it would cause the compiler to bail out. I'm thinking that copying libgdx's reflection code might be the way to go though - since @Mapper would break compilation otherwise.

@PackedWeaver and @PooledWeaver can be safely ignored, since they'd act as normal components if not woven.

Reply to this email directly or view it on GitHubhttps://github.com/junkdog/artemis-odb/issues/56#issuecomment-34417892 .

junkdog commented 10 years ago

Ok, good that annotations themselves aren't a problem. Should look into extracting libgdx's gwt-reflection code - it would be a shame if special measures had to be taken when cross-compiling to GWT.

Flet commented 10 years ago

When is ArtemisCon? We have quite a crowd on this thread now :)

Flet commented 10 years ago

Sweet, artemis-odb with @Mapper annotations... working in GWT :) http://brokenspork.net/

Prepping branch now.

junkdog commented 10 years ago

Awesome! Heh, time to wrap up this release ;)

When is ArtemisCon? We have quite a crowd on this thread now :)

Hell yeah! The fictitious CEO tells me she booked a private jet which will take us somewhere warm.