Issue with final field serialization (networking + zay-es-net)

Hi

This is an example PersistentComponent component with a final field.

import com.simsilica.es.PersistentComponent;


public class PlayerCharacter implements PersistentComponent {

    private final boolean leader;

    public PlayerCharacter() {
        // No-Arg Constructor usde for JME Serialization
        this(false);
    }

    public PlayerCharacter(boolean leader) {
        this.leader = leader;
    }

    public boolean isLeader() {
        return leader;
    }
}

and I am registering it using a FieldSerializer:

Serializer.registerClass(PlayerCharacter.class, new FieldSerializer());

I add it to the player character (while setting the field to true) when I initialize it for the first time.

        GameEntities gameEntities = manager.get(GameEntities.class);
        gameEntities.createEntity("Doha", ed.createEntity(),
                new SpawnPosition(0, 1, 0),
                new PlayerCharacter(true)
        );

I can save/load it from SqlEntityData without issue.

The problem is when I look for it from the client-side using an EntitySet the field is “false” (set by no-arg constructor) instead of being “true”.

If I remove the final modifier from the field then it works fine.

Would it be possible to add support for final field serialization in JME’s networking layer?

No-Arg Constructor usde for JME Serialization

Java Serialization(deserialize) do not call Constructor, so im not sure if this “false” is taken from constructor.

Do JME Serialization do some tricks to call it? like custom “readObject” ?

I think so, see FieldSerializer. New instance created by constructor (no-arg constructor).

But then I am expecting it to through IllegalAccessException because the field is final but it does not!!

Probably I am missing something here :roll_eyes:

1 Like

and i understand there were no Errors, because i think there might be problem for Reflection to set Final field (it require tricks to set it). Maybe JVM do not allow reflection set it even via constructor call, idk.

Or constructor set it, but it cant later change it to “true” due to JVM security reasons.

Let me try with an integer field instead of boolean and see the result, that might make it a bit more clear. :slightly_smiling_face:

look here:

    if (Modifier.isTransient(modifiers)) continue;
    if (Modifier.isFinal(modifiers)) continue;
    if (Modifier.isStatic(modifiers)) continue;

it just skip:

so generally it follows:

  • Serializable set it correctly “true / false” (depends on server value)
  • Constructor set it to “false”
  • Unable to set to “true” again since its not in “savedField”
1 Like

Oops!!

And now i belive Paul had reasons to do so. (when allow it will anyway Throw Error in 172 line)

Anyway why you need Constructor to set it “false”? Cant just use serialized value?

Of course.

Serializer requires a no-arg constructor and because the field is final I must pass some default value to it.

btw, could use Boolean instead boolean.

But anyway its not a problem in this case, since if im not wrong readObject override default serialization. So if Paul skip final fields, it just skip them anyway. (it could be anyway problem for Reflection to read them, even if setAccessible is true)

Now i feel like i should remind myself all reflection-security topic hehe.

Seems like we need wait for @pspeed reply.
But i guess it will be “you cant use final fields, because…”
(or at least use final fields, but not for serialize purpose)

Tho as i remember, default Java Serialization is able to serialize/deserialize final fields.

1 Like

I see.

Actually my point was to see if I can convert my components to Java Record (added in Java 14) because it fits the design better for immutable objects and remove most of the boilerplate codes. (implicitly all fields are final in records and compiler add getters automatically and there is no setter).

May be we need a RecordSerializer for this specific purpose.

2 Likes

nice. Yes, it would be helpfull in this case for final fields to be working.

1 Like

Yes, also Zay-ES SqlEntityData does it without problem as well when loading components from the database.

Ok, I just tried it locally by commenting this line

It works fine and there is no exception probably because of

Also, I converted my component to a record and it works fine now :slightly_smiling_face:

public record PlayerCharacter (boolean leader) implements PersistentComponent {

    public PlayerCharacter() {
        this(false);
    }
}
1 Like

then i wonder what reason Paul had to forbid final fields.

1 Like

Paul did not write the crap that is the JME serializer stuff… only patched it here and there so it was usable.

My one regret when rewriting spider monkey was not rewriting this package. Since according to some it was “working perfectly”, I chose to concentrate on the things that were horribly broken.

Though if I were to rewrite it today, it would be much better than if I rewrote it back then. It is still on my to-do list to make a bit stream based implementation.

5 Likes

@pspeed so can I make a patch for allowing final fields?

1 Like

Sure, if it works then I don’t see why not.

2 Likes

It works in my test.

By the way, my client and server are inside the same VM (embedded server) do I need to test the case where the client and server are separated or that is not relevant?

Should be the same, I guess.

1 Like