setUserData() vs. Controls: Which is best practice?

I’m new to jMonkey (loving it 100% so far) and going through as many tutorials and guides as I can find on the Internet. As I’m learning, I’m trying to wrap my head around jMonkey’s design and how I should design my games to complement the way jMonkey does things. Now I’m at my first impasse.

Here’s how I understand it:

  • setUserData() allows an infinite number of variables to be set on one particular Spatial.
  • Controls define data and behaviors that can be replicated across many Spatials.
  • AppStates define a set of data and behaviors that are overarching to all Spatials in the scene.

I understand the difference between AppStates and Controls, but I’m having a hard time seeing why anyone would ever use setUserData() when they could just create a Control instead? Are there any use cases where setUserData() is actually preferred?

The controls contains code that is relative to the specific spatial they are applied to, for example if you have a jump pad you could have a JumpPadControl that makes the player jump when he touch it or if you have a windmill, WindmillControl could contains the code that makes the blades move.

The userdata contain variables like Health Armor etc… of the specific spatial.

AppStates are just a fancy way to subdivide the code for example

  • MenuAppState: will show the main menu
  • GameAppState: will start the game
  • FPSAppState: will show the first person hud and will bind w, a, s, d for walk and space for jump
  • VehicleAppState: will show the vehicle hud and will bind w for accelerate, s for decelerate, a and d for left right and space for brake

Also, by looking at the code, you can only store primitives in .setUserData();

No, you are reading the wrong code…

[...]
public void setUserData(String key, Object data) {
    if (userData == null) {
        userData = new HashMap<String, Savable>();
    }

    if(data == null){
        userData.remove(key);            
    }else if (data instanceof Savable) {
        userData.put(key, (Savable) data);
    } else {
        userData.put(key, new UserData(UserData.getObjectType(data), data));
    }
}
[...]

and

[...]
public UserData(byte type, Object value) {
    assert type >= 0 && type <= 4;
    this.type = type;
    this.value = value;
}
[...]

From

and

2 Likes

Why not just have another control called StatsControl with fields for health, armor, etc. and no behavior? Whenever you create a spatial, rather than using setUserData() on that spatial, just addControl(StatsControl) instead. When retrieving data, you can use getControl(StatsControl.class).getHealth() rather than getUserData(“health”). Isn’t that essentially the same thing?

One benefit I see with using controls over setUserData() is that you can alter the StatsControl class and it will apply to all relevant spatials, whereas if you want to change the structure of data held with setUserData(), you need to go through the code and change every instance of setUserData()/getUserData(). What if you wanted to rename “health” to “hitpoints”? That’d be a pain, wouldn’t it?

I hope I’m making sense here. I just don’t see the purpose of setUserData() because I feel like everything can be accomplished through controls. Hopefully somebody can show me the error in my thinking?

Why would you want to have two extra update methods called every frame just to store data?

Usually, I only use setUserData() to store the ID of my game object… and it’s the game object that has all of the health, etc… But I guess it’s very popular to mix visualization and game objects together into a big unruly mess… in that case, I guess add as many controls as you want until updateLogicalState() takes several frames to complete. :wink:

4 Likes

Is Java/JME really so bad that empty function calls will bring it to a crawl? Even a million empty function calls should be nothing. Doesn’t seem like it’d be an issue in the real world, or would it? Maybe my understanding is incorrect.

Anyway, perhaps you could help me see the benefit of setUserData() by explaining how you use it in an actual game? You say that you store the ID of the game object. By that you mean a globally unique index integer every time you spawn a new spatial? How does this help you? Is there a method that allows the selection of a spatial based on its UserData?

I have game objects. Those are the things in the game that actually do stuff. In my case, they are Entities in an Entity System (search: Entity component system or my OSS library Zay-ES).

Whether those are visualized at any given moment or not may depend on several things.

I store the ID in the user data for the relatively rare cases where I need to go from spatial to game object (like when things are clicked on). Mostly things go the other way. Game logic runs on game objects in whatever time it wants. Then there is some app state or control that updates the visualization with the latest state.

Two method calls per control per frame may be cheap but it’s not free. When there is a mechanism to do the same thing absolutely for free without any cost whatsoever, why would one even bother with a control? If your data is purely data you could just store it as UserData instead of adding it as a control.

Anyway, in new JME, you can get optimizations for sections of the scene without any controls. In 3.0, updateLogicalState calls updateLogicalState for all of the children which call their children and their children and so on. Each level iterating through whatever controls are there and calling controlUpdate(). (A separate pass calls controlRender().)

In 3.1, this has been optimized such that if a section of the scene graph has no controls then it isn’t even traversed.

Whatever the case, it seems to me that there is no benefit at all, not even one bit, to using a control just to store data… but there is a slight downside, especially if you start to use a lot of them. So why bother?

1 Like

Thanks for the detailed reply. Very much appreciate it!

For some reason I was under the impression that setUserData() could only set primitives, so int, float, boolean, etc. because all of the examples I’ve seen only used primitives. Can I store entire objects with it? If so, that changes everything! I was looking at Controls as a way to encapsulate data in one place, but if I can store objects with setUserData(), that addresses the issue I was having.

As stated earlier in this thread by code posts, setUserData() takes objects… which may be autoboxed primitives, Strings, or any classes that implement Saveable.

1 Like

No idea what Saveable is, so I guess it’s time to do some research. Thank you.

Edit: Here we go. Seems simple enough: http://wiki.jmonkeyengine.org/doku.php/jme3:advanced:save_and_load

I just made an abstract class that extends Node and put a lot of my unit variables there then I initialize the instantiated class with the loaded spatial and replace the default root node from the loaded spatial with ‘this.’

Thats a whole lot of work and strange design decisions just to not have to implement Savable.

1 Like

I usually assume people have access to the javadoc so I don’t automatically include links.

And you don’t actually have to implement Saveable for real. Just implement the interface and stub out the methods with empty implementations (or throw UnsupportedOperationException). Since you won’t be saving/loading your spatials (probably) then the requirement to implement Saveable is just arbitrary… do the minimum effort.

1 Like

It had nothing to do with implementing or not implementing savable, neither was it very much work at all. Extending the Node class and adding my entity variables and methods there made it much easier because I didn’t have to jockey between Spatial and Control, I didn’t, as pspeed mentioned, clutter up the control loop with empty update calls and neither did I have to instantiate loads of other objects to use with setUserData().

The Node class was going to be instantiated with every unit anyway, this way I only instantiate that class without having to instantiate another object for each and every unit variable for each and every unit.

And how are you going to do when it happens that you need a different kind of unit? Extending another time Node with different fields?
And i guess your loading code will end up as large cascaded if/switch
And for accessing the data you have to use casts all over the place.

There is a reason that lots of gamedevs switch over to entity systems, mainly becaue sooner or later you hit design limits with a composition/inheritance based system.
Usually the first sign of such a limitation is that you have to duplicate code.

Note: Not saying that your system does not work, if it works for you fine. Just saying that in general there are more flexible ways of dooing the same

Architecturally, it’s like a JTextField having direct JDBC connections, though. View and model are so intertwined that they can never be separated.

JME discourages extending Node but especially if it’s just to make your game object.

One should always prefer composition over inheritance, really.

However, the foundation of this whole conversation is itself a bad practice so there may be no good solutions. At least with the setUserData() approach, you could have different objects with different sets of things without having all kinds of Node subclasses (composition over inheritance.) And at least there would also be hope of fixing the architecture down the road to decouple visualization from the game objects.

@zzuegg: The Unit class I used to extend Node is abstract so yes I create different classes that subsequently extend the Unit class and it is probably rather use case specific.

However the various different classes don’t need to be cast that often, most of the time working with the objects as Unit is all that is necessary because all the units have hit points, they all have attack ranges and visibility ranges. Really the only time I need access to specific methods is when dealing with a Space Station or Cargo Ship and that’s generally just when building/repairing units.

@zzuegg & @pspeed Very interesting as this ties in a bit with an email I received from a friend today discussing Taoism:

“What is good for one individual might not be good for another, or good for a single individual at different times. The same goes for beauty, truth, usefulness, and so on. Therefore, rather than dogmatically maintaining constant standards, one should be prepared to flexibly adjust one’s attitudes in relation to the needs of the current situation.”

Paulism: “Sometimes when people warn you about a path it is because they have traveled that path 500 times already themselves and know that there are hungry tigers at the end of it. They hope to save you from losing the limbs and body parts they’ve already lost.”

Sometimes, having a big bite taken out is the only way to learn, I guess… we did that 500 times, too.

Yeah, but not everyone has the same experience with tigers, maybe they were there when you traveled the path, but have since moved on.

I’ve already implemented this technique and it works effortlessly for my purposes. Which is to say I’ve walked that path too and can say with certainty I still have all of my limbs.