Removing param from MatDef file causes embedded material to crash on load

If I remove a parameter from a shader’s .j3md file, I have found that it will cause all of the materials using that shader to crash if they still have a value assigned to that paramater in the material file.

As a result, it can become impossible to remove broken or deprecated variables from a shader without having to also update every single material that uses that shader - which sometimes is not possible if the material is embedded into the j3o

Here is the error that is thrown when a material detects a variable defined to a paramater that is not defined in the .j3md file:

com.jme3.asset.AssetLoadException: An exception has occurred while loading asset: /Materials/Vehicles/Mountains.j3m
	at com.jme3.asset.DesktopAssetManager.loadLocatedAsset(DesktopAssetManager.java:262)
	at com.jme3.asset.DesktopAssetManager.loadAsset(DesktopAssetManager.java:374)
	at com.jme3.asset.DesktopAssetManager.loadMaterial(DesktopAssetManager.java:395)
	at com.jayfella.jme.vehicle.examples.worlds.Mountains.load(Mountains.java:98)
	at com.jayfella.jme.vehicle.World.attach(World.java:97)
	at com.jayfella.jme.vehicle.simpledemo.HelloMav.simpleInitApp(HelloMav.java:113)
	at com.jme3.app.SimpleApplication.initialize(SimpleApplication.java:239)
	at com.jme3.system.lwjgl.LwjglWindow.initInThread(LwjglWindow.java:513)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:625)
	at com.jme3.system.lwjgl.LwjglWindow.create(LwjglWindow.java:466)
	at com.jme3.app.LegacyApplication.start(LegacyApplication.java:463)
	at com.jme3.app.LegacyApplication.start(LegacyApplication.java:424)
	at com.jme3.app.SimpleApplication.start(SimpleApplication.java:127)
	at com.jayfella.jme.vehicle.simpledemo.HelloMav.main(HelloMav.java:92)
Caused by: java.io.IOException: The material parameter: ProbeColor is undefined.
	at com.jme3.material.plugins.J3MLoader.readValueParam(J3MLoader.java:422)
	at com.jme3.material.plugins.J3MLoader.readExtendingMaterialParams(J3MLoader.java:441)
	at com.jme3.material.plugins.J3MLoader.loadFromRoot(J3MLoader.java:771)
	at com.jme3.material.plugins.J3MLoader.load(J3MLoader.java:800)
	at com.jme3.asset.DesktopAssetManager.loadLocatedAsset(DesktopAssetManager.java:260)
	... 13 more

Specifically this line is where it is happening:
at com.jme3.material.plugins.J3MLoader.readValueParam(J3MLoader.java:422)

This is the code that lies there to check if the param is null, and for some reason throws an error if so:

I think that it would be better to replace this code with a warning instead. Otherwise refactoring old shaders to remove unused variables will not be possible without a lot of extra work editing each material file.

I am curious what the rest of you may think about this proposed change?

The situation definitely needs some kind of diagnostic message. I’m not convinced it needs to throw an exception however. And throwing an IOException seems to me a very strange choice indeed.

1 Like

Remember, j3o is not an interchange format. It’s a ‘compile this for my game distribution’ format.

So when a j3o gets busted, the usual answer will be “just regenerate it”.

And if the response to that is “but I made lots of changes to it”… then that’s an asset pipeline problem that I recommend fixing. (Note: this was/is a huge problem with the SDK’s scene editor, too.)

JME considers this particular error the same as if you try to reference some bad field in a Java class… in this case it’s like a “parser error” which is why it’s an IOException. Your j3m is invalid because it has a typo in it.

Migrating is a pain but I don’t know if treating it like a warning is the right answer either. That pretends that the most common case is yours when in case the MOST common case is “user entered the wrong value when making the j3m” and that should fail fast and loud.

2 Likes

I may have been wrong to suggest just a warning, but something other than throwing an error that crashes the app seems to be necessary here.

I’m guessing this is why the sdk assigns a yellow texture that says “missing material” when you enter an invalid asset key, so it can still be fast and loud, but does not cause the app to crash.

But in this case with the material param in the matdef file, it seems like the only choice is to throw an error or to print some other type of lesser warning unless someone can think of another intuitive solution like the yellow “missing texture” solution

Unfortunately this is impossible for games that have big static scenes that were arranged in any JME based editor and have to be in j3o format. To avoid busted scenes, I just make frequent backups and if I do something wrong, I make sure not to do it again. But this is not like that, since this is a case where changing or removing a variable in my my PBRTerrain shader would also bust other people’s scenes.

It is also unfortunate that an IDE’s functions for refactoring code does not work the same for refactoring assets, otherwise this would not be nearly as huge of a problem.

changing or removing a variable in my my PBRTerrain shader would also bust other people’s scenes

The incompatibility caught me by surprise because the version number went from v1.0.2 to v1.0.3, so I didn’t expect it to break anything.

As you probably already know, when a breaking change occurs in released software, it’s customary to increase the major version number, for instance from v1.0.2 to v2.0.0

I realize jMonkeyEngine has not set a good example in this regard!

To my mind, patching a J3M asset (which is ASCII text) is a trivial matter compared to patching a J3O (which is binary).

1 Like

A.B.C

increments in C = bug fixes only… no breaking changes.

increments in B = new features, may be breaking changes.

increments in A = totally new architecture, all bets are off. Probably no straight-forward migration path.

That’s the way most of the world operates. (And why we call JDK 8 “8” even when it’s 1.8)

JME mostly follows this. We try really hard anyway… to the point where we’ve left bugs in .C release increments because fixing a bug could break some reasonable number of applications.

I think when we’ve failed it was genuinely an accident.

3 Likes

I will keep that in mind for future versions, I’ve actually wondered what justifies changing A B or C in the versioning, so that’s useful to know. Although I think i could still mess it up sometimes, since in this case I overlooked that this was a breaking change until after I uploaded it, but I’ll do my best in the future.

Yeah I think it actually becomes impossible to fix a j3o when the material is embedded into the j3o file, because the j3o will no longer open up once this error occurs, and opening the j3o would be the only way to ever access the embedded materia as far as I’m aware.

I am not particularly worried about this issue with my pbr terrain shaders since I’ve already managed to do the refactoring I needed, thankfully before anyone else decided to use my shaders for big scenes that would take them lots of time to fix. So for me this issue just means I will not be able to rename some variables in my shaders that could have better names, which sucks but isnt a big problem at all.

I mostly was worried that this error might become a problem in the stock jme shaders like the stock PBRLightng.frag if we ever need to rename or remove a variable there, and then that would cause many users to have to refactor all of their material files using pbr, including j3o files with embedded materials. But I guess that type of change to a stock shader would also be rare.

1 Like

Normally, one’s asset pipeline is setup to rebuild j3o files for updating things like this. The j3o should not be thought of as a master copy, it is more a ‘compiled’ asset for distribution. You should always be able to rebuild your j3o from the original assets, if not you have an issue in your workflow/pipeline.

EDIT: I will elaborate, it is like having a .class file and when you rename a function in your code, you do not edit the .class file, you edit the .java file then recompile. The same should work for taking models/scenes to j3o.

2 Likes

I’ve made a scene editor in JME and have no choice but to store my scenes in j3o format, specifically each 512x512 section of the world is stored in its own j3o scene.

I could theoretically write the scene to json files where I store each model’s material, location, rotation, and scale as well as user data so the scene can be reconstructed. But at that point it would be overkill since that’s really what a j3o already does with linked assets, and that would also require making terrains without embeded materials, which would also make this problem much easier to fix in the first place.

This is true but not for materials that are embeded into the terrain/geometry I think, since the material is no longer in an editable text format when it is embedded in j3o format - unless I’m mistaken, correct me if I’m wrong.

It sounds like the answer to this problem is that leaving materials embedded in a j3o file is a terrible choice and should be avoided at all costs. And I guess this is a bigger problem for terrains since the SDK and other editors like JMB have always just left the material embeded into the terrain upon creation.

This is inherently a tooling issue at that point. For example, in Outside, the entire world(s) is stored in a database:

The world is then compiled to either json, or j3o for distribution.

But, if one was to actually open the j3o in a text editor, they would fine that strings are left intact as ascii text.

3 Likes

I did not know this, I was wrong then to say it is not possible to edit an embeded material. It looks like all the material info embeded into the j3o is all left in text format, so if I experience this problem again I should be able to do a find/replace action on a group of j3o files and materials. Thanks for the tip :slightly_smiling_face:

I am curious, do you mean that you store all of the model’s info like loc/rot/scale/material key in the database and then construct the scene based on these paramters at run time? I thought this was the idea behind linking an asset in a j3o file, so I am curious what is the advantage of storing it in a database rather than a j3o format.

I know that your way would be more modular and avoids the possibility of a corrupted j3o scene without requiring frequent scene backups, but I am curious if there are any other advantages as well, since it sounds like my way of dividing a big scene into many 512x512 j3o scenes is not good practice.

By storing it in a database, I can take it to any format I want. I can also perform bulk modifications, and save space by relating definitions instead of repeating them.

For example, the models table:

CREATE TABLE models (
    name VARCHAR(100),
    mod MODEL,
    PRIMARY KEY (name)
);

It uses type MODEL, defined as:

CREATE TYPE MODEL AS (
    resource VARCHAR(512),
    embeddedResources VARCHAR(512)[],
    scale REAL,
    /* location */
    x REAL,
    y REAL,
    z REAL,
    /* rotation */
    i REAL,
    j REAL,
    k REAL, /* rotation and location offset for the model */
    w REAL,
    geos GEO[],
    nativeMat BOOLEAN
);

It has a resource field that points to the model, in most cases this will be something like Models/some_name/some_model.gltf take note this is in the format for the asset loader to locate it.
The embeddedResources is a list of resources that the model may reference internally that are not registered in the database.
geos is an array of GEO type, which describe each geometry in the model and what to do with them.
nativeMat is for models that contain their own material definition internally. For example a .mtl for a .obj

The GEO type defines metadata about the geometry in the model:

CREATE TYPE GEO AS (
    name VARCHAR(100),
    mat VARCHAR(100),
    animations VARCHAR(100)[],
    morphs MORPH[]
);

The mat field references which material definition in the database to use when loading the geometry.
The material table:

CREATE TABLE materials (
    name VARCHAR(100),
    mat MATERIAL,
    PRIMARY KEY (name)
);

which contains type MATERIAL:

CREATE TYPE MATERIAL AS (
    shader VARCHAR(100),
    mat_textures MATERIAL_TEXTURE[],
    mat_textures_array MATERIAL_TEXTURE_ARRAY[],
    mat_floats MATERIAL_FLOAT[],
    mat_colors MATERIAL_COLOR[],
    mat_ints MATERIAL_INTEGER[],
    mat_bools MATERIAL_BOOLEAN[]
);

shader references which shader definition in the database to use. The rest are the parameters to pass to the shader.

The shader table:

CREATE TABLE shaders (
    name VARCHAR(100),
    sha SHADER,
    PRIMARY KEY (name)
);

And type SHADER:

CREATE TYPE SHADER AS (
    mat_definition VARCHAR(512),
    fragment VARCHAR(512)[],
    vertex VARCHAR(512)[],
    lib VARCHAR(512)[]
);

mat_definition points to the .j3md. The rest point to any shader files that are used in the shader.

The advantage of this, is that when Outside is running as a server/client pair, I stream the database information in serialized beans to the client, and both the server and client know exactly which resources are required to load any model (or any asset for that matter).
I can also make bunk edits on the database to change things like material parameters, or which shader I want to use, etc… I can also replace what model I am pointing to without rebuilding the entire definition, like if I made an update to the player mode, I just point the resource to the new version of the model.

I also can export to .j3o or .json and include with the files all the assets needed external to the .j3o. For example, when building Outside’s asset packs, it builds a json file with the database definitions, and puts that file along with any necessary assets into a zip file. Which is then can import into another server. The server will compile models into j3o if requested to for loading performance on the client, or the client has a utility to create the j3o from the models when uploading them to the server. All editing of the database is done right from the game client using a set of gamemaster UIs.
This structure also allows me to edit assets while the game server is running, the server then notifies the clients of the modified assets and asset definitions from the database.

1 Like

I should note,
Models are not placed directly in the game world, objects are in the game world, which have a reference to a model, along with other information:

CREATE TABLE objects (
    name VARCHAR(100),
    display VARCHAR(100),
    model MODEL_REF,
    stats STAT_REF[],
    interactable BOOLEAN,
    physicsType VARCHAR(100),
    mass FLOAT,
    friction FLOAT,
    restitution FLOAT,
    PRIMARY KEY (name)
);

Then the objects are related to the world:

CREATE TABLE world_objects (
    id BIGINT NOT NULL DEFAULT nextval('outsideId'),
    loc LOCATION,
    object VARCHAR(100),
    scripts SCRIPT[],
    stats STAT_REF[],
    PRIMARY KEY (id)
);
CREATE INDEX ON world_objects (((loc).world_id));

The object is reusable multiple times in the world.

2 Likes

There is a way to save j3o in XML form. I’ve never done it. But IF I were writing an editor and was crazy enough to want to use JME serialization as my storage format, I’d definitely look into the xml form.

(supposedly that’s the reason they didn’t just use Java’s serialization which is superior in almost every other way.)

2 Likes

I guess I am lucky that I haven’t run into too many issues using j3o for all my scenes so far that this is the first I’ve thought about it, but when I have time to work on my editor again I’ll have to consider implementing your suggestions some way or another.

The only downside to moving away from j3o storage would be that I could no longer edit chunks of each scene in the SDK or other editors, and would be limited to using only my editor. So I would have to wait until I finish a few more tools in my editor that I’ve still been relying on the SDK for. I initially thought using j3o was a good idea because it was transferable between different JME based scene editing tools.

In the future, at the very least I think I could benefit from writing some quick code to save/load each of my j3o scenes in a json format, then compare that to the saved j3o version (if it exists and isn’t corrupt) and update both appropriately. So the scenes would primarily be json based, but i could still edit the matching j3o file in editors like the sdk if necessary.

It is often the case where we think “this way won’t work so I must HAVE to do it this other super hard way”.

That’s not the case here, really. If your editor supported j3o and the XML form of j3o then you could export to j3o to edit in the scene editor then read it back in to store in XML. Your editor could use the XML form by default with almost no change, I guess and then at least you have the fallback of hand editing the files.

A quick git search found this:

1 Like

Oh I think I misunderstood part of your last post. I thought you meant that you would still consider the xml form crazy.

The classes you linked look like they should be fairly straightforward to implement as well, so I’ll try using that to back the scenes in XML when I eventually go back to working on my editor.

I tried using the XML format and encountered bugs, so I wouldn’t recommend it.

1 Like

Still, I would think fixing the problems in the XML exporter/importer are probably easier than designing a custom format.

It kind of is… but if it works then it’s much less crazy than j3o which is nearly guaranteed to break sometimes because it was never designed for this purpose.

In my own libraries, I try to follow the rules of Semantic Versioning.

3 Likes