jueves, 13 de agosto de 2015

Nifty-flow explanation

My first open-source collaboration!


When I started "Cabo Trafalgar", it took me three tries to find the technology that made it possible. I started with Irrlicht, and I also took a look to another library I cannot recall anymore. Only when I discovered that the language that paid my bills was really able to render native 3d and really nice water I adventured on JMonkeyEngine with Java.

Once you start with JME, you don't have many options for creating rich graphic interfaces, and the one that highlights is, needless to say, nifty-gui. Easy to use (I find easier to make examples using JME3 than raw nifty-gui libraries, that easy!) and good documentation and examples. But no doubt it was my webby background I absolutely missed some features:
  •  When you're defining a screen, you're pretty much defining its connections, in my words, "flow of screens". That actually reduces by a lot the possible re-usability of screens in other parts of your game, or other games whatsoever.
  • You can create screens by static xml, or by dynamic java, but web developers have long gone over these restrictions, and we have like a dozen template languages to mix code and static text. At the end of the day, xml for screen is nicer and easier to interpret than crude java code (no offense).
  • Remember, when enumerate, always use 3, 7 or 10 bullet dots, force it if necessary :P
At this point, I had little options, I could have dropped my project, but I didn't feel like doing it again. I could have renegotiated my use cases to make it more cumbersome to manage repeated screens in different parts of the game, or... I could actually build what I need on top of nifty-gui.

For first time I was actually solving my own problems instead of waiting for somebody else to make that for me, and it didn't take me long to realize that it was actually possible.

The use case:

For my game, I needed the user to walk one or two screens, one after the other, until find the "hall", a screen that contains every mode the player can play. In my case, I have a "CounterClock" game and a "Windtunnel" game, both accessible from the same "Menu" screen.

Once you choose "CounterClock", user must "Select profile", then "Select ship", then "Select map" and finally "Select controls", then play.
Once you choose "Windtunnel", user must "Select ship", then "Select controls", then play.

It's easy to spot that I really wish to reuse some screens, and the flow of screens will be linear most of the times in as many examples I can think.

Implementation:

Just take the base of Struts, you have a central controller that will receive every interaction with the user. This controller has, somewhere, the capacity to decide what are the user's options given the current status, and using the current input, calculate the next screen to render.

That exact idea made me build the RedirectorScreenController, all it does is redirecting the user to the next calculated screen, from the ScreenFlowManager. My screens will always onNext and onPrevious actually direct to this RedirectorScreenController and it's associated empty screen.

Next, as I'm talking about reusability, I need to differentiate between "screen definition" and "screen instantiation", and bear in mind that the relationship will be 1:N.
My implementation defines a "screen definition" (See ScreenDefinition) as:
  1. a unique name
  2. a way of getting the controller live instance (unique in the system, so far)
  3. a way of getting the screen constructor (a class that actually knows whether the screen is xml or java and executes it)
My implementation defines a "screen instantiation" (See Screen) as:
  1. the flow it belongs to
  2. a name, unique within the flow
  3. the live instance of the controller associated to this screen.
  4. the live instance of the generator associated to this screen.
Every screen has a uniqueScreenId, made from the flow name and the screen name.
In case the flow name is not set, we can safely assume we need to search that screen in our current flow (local search).

Finally, this theoretical system was actually becoming alive, we just need to define flows.
  1. A name for the flow, unique
  2. A sequence of screen definition names
  3. An optional screenUniqueId parent
We need an initial flow, that's the flow without a parent, we can only have one, the ball is rolling!

If we declare more flows, we need to "hang" them from a concrete existing screen, either from the root flow, or another flow.

We can always query the ScreenFlowManager (See ScreenFlowManagerImpl for implementation details) for our options, it'll tell us whether we can continue forward, and if other flows are available from this screen.

Internally, a JGrapht (in-memory graph database) instance have all the information to know where are you, what are your options and what's your next state from current state and input.

User input

Maybe it's a bit annoying, but there is at least another element to cover. How do we communicate the ScreenFlowManager what's our next move?

I only managed to solve this problem by "injecting" the same ScreenFlowManager into the controllers (not even automagically) and allow a "setNextScreenHint" method to tell the flow manager what's your intention.

Instance resolution

I gently let this for the end.
Do you remember I mentioned "a way of getting the controller live instance" when I was talking about ScreenDefinition?. I was ambiguous because this library is quite technology agnostic, so much, that you have to provide a way of telling me how to get your live instances.

The easier example, LiveInstanceResolutor, it's an object you feed with "instance name" and "instance", you give it to me, and every time you mention the instance name, nifty-flow will take the instance from the resolutor. It's a f*****g map.

StaticScreenGeneratorResolutor will take your static xml directly, that work is done already.

Nifty-gui provides, however, other resolutor, DefaultInstanceResolutor, nothing more than a "resolutor of resolutors" so you can use several at the same time if you wanted. Find in the example below how you can assign a prefix to later use in your Screen Definitions.

The origin of this mechanism is because I cannot live without Spring, so every time I needed an instance, I'm actually invoking a resolutor that is digging inside the BeanFactory for the right bean, autowired, resourced, initialised and ready to use. This implementation is not provided to avoid Spring dependencies, I'm sorry ;)

How's that looking so far?

public interface ScreenFlowManager {
    String NEXT = "next";
    String PREV = "prev";
    String POP = "pop";

    void addScreenDefinition(ScreenDefinition screenDefinition) throws InstanceResolutionException;

    void addFlowDefinition(String flowName, final Optional<String> screenNameFrom, List<String> flowDefinition);

    String nextScreen();

    void setNextScreenHint(String nextScreenHint);

    Collection<String> getChildren();
}

I wouldn't complain much, 5 method, all them explained above. Easy to use!

Working example (please find the entire code here)

public void simpleInitApp() {
    NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(
            assetManager, inputManager, audioRenderer, guiViewPort);
    Nifty nifty = niftyDisplay.getNifty();
    guiViewPort.addProcessor(niftyDisplay);
    flyCam.setDragToRotate(true);

    nifty.loadStyleFile("nifty-default-styles.xml");
    nifty.loadControlFile("nifty-default-controls.xml");

    DefaultInstanceResolutor defaultInstanceResolutor = new DefaultInstanceResolutor();
    ScreenFlowManager screenFlowManager = new ScreenFlowManagerImpl(nifty, defaultInstanceResolutor);

    LiveInstanceResolutor liveInstanceResolutor = new LiveInstanceResolutor();
    defaultInstanceResolutor.addResolutor("static", new StaticScreenGeneratorResolutor(nifty));
    defaultInstanceResolutor.addResolutor("live", liveInstanceResolutor);

    RootScreenController screenController = new RootScreenController().setScreenFlowManager(screenFlowManager).setApplication(this);
    ScreenController screenController2 = new Controller2(screenFlowManager);
    ScreenController screenController4 = new Controller4(screenFlowManager);
    liveInstanceResolutor.addController("root", screenController);
    liveInstanceResolutor.addGenerator("root", new RootScreenGenerator(nifty, screenController, screenFlowManager));
    liveInstanceResolutor.addController("controller1", new Controller1(screenFlowManager));
    liveInstanceResolutor.addController("controller2", screenController2);
    liveInstanceResolutor.addController("controller3", new Controller3(screenFlowManager));
    liveInstanceResolutor.addController("controller4", new Controller4(screenFlowManager));

    liveInstanceResolutor.addGenerator("generator2", new Generator2(nifty, screenController2));
    liveInstanceResolutor.addGenerator("generator4", new Generator4(nifty, screenController4));

    try {
        screenFlowManager.addScreenDefinition(new ScreenDefinition("root", "live:root", "live:root"));
        screenFlowManager.addScreenDefinition(new ScreenDefinition("screen1", "live:controller1", "static:/screen.xml"));
        screenFlowManager.addScreenDefinition(new ScreenDefinition("screen2", "live:controller2", "live:generator2"));
        screenFlowManager.addScreenDefinition(new ScreenDefinition("screen3", "live:controller1", "static:/screen.xml"));
        screenFlowManager.addScreenDefinition(new ScreenDefinition("screen4", "live:controller4", "live:generator4"));

        screenFlowManager.addFlowDefinition("root", Optional.<String>absent(), newArrayList("root")); 
        screenFlowManager.addFlowDefinition("screenFlow1", of("root:root"), newArrayList("screen1", "screen2", "screen3", "screen4"));
        screenFlowManager.addFlowDefinition("screenFlow2", of("root:root"), newArrayList("screen1", "screen4"));

        nifty.addScreen("redirector", new ScreenBuilder("start", new RedirectorScreenController().setScreenFlowManager(screenFlowManager)).build(nifty));
        nifty.gotoScreen("redirector");

    } catch (InstanceResolutionException e) {
        e.printStackTrace();
    }


}

In few words:
  1. Create and feed your resolutors with the information you'll need from the screen definitions.
  2. Create your Screen Definitions, I have defined 5 screens, two of them from the same XML and other three generated from java code. Here lays the potential of nifty-flow, the static screens would be reusable even across projects, without knowing what screen to link forwards and backwards.
  3. Create your Screen Flows, I have a root one with one screen, then from that screen, another two reusing some screens.
  4. Add redirector (with that name) as "start" and let the magic happen.
 Final words

There's some more work to do. I implicitely create NEXT and PREV links between screens, but I'd rather have implemented a mechanism to push every movement to a stack, and a POP would always return to the previous screen, this would allow us to jump to the middle of another flow and being able to come back (PREV is "statically" linked to the same screen, while POP could be any invoker).

From the features I was missing in nifty-gui, I still keep missing some help templating XML files, instead of making complex screen from java only, that's something I'd thrill to implement, but god knows I don't have the time now.

Hope you enjoy of this library

Alber

sábado, 2 de mayo de 2015

Some lessons learned: Gradle



After a year dealing with Gradle in different companies, I think it's high time to start collecting some patterns and suggestions.

1) Avoid redundant folder declaration.

Remember, apply plugin java already sets up some defaults folder, you don't need to declare them if you don't plan to change them.

Recommended
apply plugin: 'java'

Redundant
apply plugin: 'java' 

sourceSets {    
    main {        
        java {            
            srcDir 'src/main/java'        
        }        
        resources {            
            srcDir 'src/main/resources'        
        }    
    }     

    test {        
        java {            
            srcDir 'src/test/java'        
        }        
        resources {            
            srcDir 'src/test/resources'        
        }    
    }
}

Groovy plugin and scala plugin also declares other folders, just read some documentation before start using them.


2) Choose your syntax for dependency management.

Gradle allows you two syntax for dependency declaration.
  • Extended (group: 'ws.lazylogin', name: 'lazylogin-core', version: '1.0.0')
  • Compact ('ws.lazylogin:lazylogin-core:1.0.0').
Be consistent, your users and colleagues will be pleased.

I prefer the compact one :)


3) Your users don't need to know project details to compile it.

Any project should be able to clean + build from the code and a JVM in place. Ideally nothing else should be required.

Although some projects will inevitably break this suggestion, try not to.

Some examples of build breaking customs.
  1. Usage of variables declared in ${user.home}/.gradle/gradle.properties that  don't have default value in build.gradle or exist in project's gradle.properties.
    • This would fail for anyone not owning / not knowing what variables to include / contaminate hi/er user gradle properties file. No no no no no.
  2.  Assumption of executables in the PATH or some predefined location.
Try your harder to make your project easy to build.


4) Prevent incorrect dependency versioning

Declare your dependencies and version in a single place (that was a good idea in Maven's universe) and reuse those dependencies by its variable name.

The risk of not following this advice is the likelihood you'll end up using different versions for the same library in different modules in the same project, and you don't want that, neither do I.

Solution would look like this:

Dependency declaration (they can be declared in a different file)
ext{   
    //VERSIONS
    v = [           
        spring: '3.2.0.RELEASE',
        jme3: '3.1.0-snapshot-github'
    ]   

    deps = [           
        //SPRING           
        spring_core: 'org.springframework:spring-core:'+v.spring,
        spring_beans: 'org.springframework:spring-beans:'+v.spring,
        spring_context: 'org.springframework:spring-context:'+v.spring,
        //JME3           
        jme3_core: 'com.jme3:jme3-core:' + v.jme3,           
        jme3_effects: 'com.jme3:jme3-effects:' + v.jme3
    ]
}

Dependency usage for any subproject
dependencies {   
    compile (           
        project(':mod-api'),      
        deps.jme3_core,           
        deps.jme3_effects,           
        deps.spring_beans,           
        deps.spring_core )
}

Instead of the more error prone way:

Dependency usage for a given subproject A
dependencies {   
    compile (           
        project(':mod-api'),                        
        'com.jme3:jme3-core:3.1.0-snapshot-github',           
        'com.jme3:jme3-effects:3.1.0-snapshot-github',           
        'org.springframework:spring-beans:3.2.0.RELEASE',           
        'org.springframework:spring-core:3.2.0.RELEASE' )
}

Dependency usage for a given subproject B
dependencies {   
    compile (           
        project(':mod-api'),   
        'com.jme3:jme3-core:3.1.0-snapshot-github',           
        'com.jme3:jme3-effects:3.1.0-snapshot-github',           
        'org.springframework:spring-beans:3.1.0.RELEASE',           
        'org.springframework:spring-core:3.2.0.RELEASE' )
}


5) Group your dependency by dependency configuration.

It's nicer :)

Dependencies grouped by configurations
compile (           
    project(':mod-api'),           
    libs.lazylogin_common_context,           
    libs.nifty,           
    libs.jme3_core,           
    libs.jme3_effects,           
    libs.spring_beans,           
    libs.spring_core,           
    libs.jackson_dataformat_yaml,           
    libs.jackson_databind
)
runtime (
    libs.nifty_default_controls,           
    libs.eventbus,           
    libs.auto_value))    

Dependencies ungrouped (and disorganized)
compile project(':mod-api')          
compile libs.lazylogin_common_context,           
compile libs.nifty,           
compile libs.jme3_core,           
compile libs.jme3_effects,           
compile libs.spring_beans,           
compile libs.spring_core,     
runtime libs.nifty_default_controls,      
compile libs.jackson_dataformat_yaml,           
compile libs.jackson_databind,           
runtime libs.eventbus,           
runtime libs.auto_value)

(Not possible when you want to exclude dependencies from dependencies).


martes, 13 de enero de 2015

Cabo Trafalgar v0.9 Released!!

Changes:
Connected with LazyLogin and RecordServer services to provide online services.
Profile manager screen.
Control selection to be ready for more than keyboard, and key selection screens.
New Ship introduced, with weight balance control apart from MainSail and Rudder.
Map selection screen now with descriptions and graphic maps for a better understanding.
Introduced tidal sea movement (sea currents) as a possible element in the maps.
Win installer.
Enhanced dynamics, more realistic.
A thousand bugs and aesthetical details fixed. Probably another two thousands created.
And an exit button!!

More information:
http://cabo-trafalgar.blogspot.co.uk/2015/01/cabo-trafalgar-v09-released.html

martes, 9 de septiembre de 2014

News in Cabo Trafalgar project

http://cabo-trafalgar.blogspot.co.uk/2014/09/not-far-from-cabo-trafalgar-08.html

Soon I'll explain about my approach for continuous delivery.

miércoles, 24 de octubre de 2012

And another conference recently watched

http://www.infoq.com/presentations/WebSocket-JMS

I liked it a lot, don't miss the example with the F1 car!

Conferences at work

In my fight for learning, make people learn, and improve the overall quality in the product and in the team, I came with an idea, to use the daily hour break for having lunch while watching a conference from infoq.com (by the way, what a great web!). I'll be recommending conferences from there when I like them, and here it comes the first (the third we visualize in our small team)

http://www.infoq.com/presentations/Java-GC-Azul-C4