Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Roblox loads whatever property is last in the file rather than whichever one is newest #329

Closed
Dekkonot opened this issue Jul 26, 2023 · 8 comments · Fixed by #332
Closed

Comments

@Dekkonot
Copy link
Member

After some testing, our assumption made with property migrations is wrong. Rather than Roblox always choosing the newer of the two properties to load, they instead seem just take load whichever property is later in the file. This is simpler but it means that the way we implement migrations is incorrect and it's only worked because we've gotten lucky.

Specifically, as of the time of writing we have three migrations:

  • Font to FontFace
  • IgnoreGuiInset to ScreenInsets
  • BrickColor to Color

When serializing properties, we sort them alphabetically by name to make sure the build is deterministic. Since in all three of these cases, the property we're migration to is after the one we're migration from alphabetically, there's been no issue. In the event that a migration is added that violates this rule though, it'll fail.

The easiest option for solving this is just to not serialize properties we have a migration for. This isn't good for projects like Lune however, so we'd need a more robust solution. To me, it feels like #277 is a valid solution here because it means we wouldn't have to worry about stripping properties (presumably migrated properties would be proxied by their migration destinations) but regardless, it's something to think about. It isn't an issue right now, so we have time.

@kennethloeffler
Copy link
Member

kennethloeffler commented Jul 26, 2023

Isn't this only a problem when the same file contains both the old and the new property (which can only occur with a crafted file)?

@Dekkonot
Copy link
Member Author

I'm not sure if it's even a real issue, but you mentioned in #328 that it might cause BrickColor to serialize and if that's the case, it's not out of the question that this could be a problem for us later. I'm not sure it's worth worrying about too much right now but it's something we should be aware of in case we add a migration where it could be a problem.

@kennethloeffler
Copy link
Member

Sorry, I probably wasn't clear enough. It is possible to create this kind of inconsistency right now:

fn main() {
    let properties = [
        ("brickColor", Variant::BrickColor(BrickColor::DarkIndigo)),
        ("BrickColor", Variant::BrickColor(BrickColor::BrightRed)),
        ("Color", Variant::Color3(Color3::new(0.0, 0.0, 0.0))),
    ];

    let dom = WeakDom::new(InstanceBuilder::new("ROOT").with_children([
        InstanceBuilder::new("Part").with_properties(properties.clone()),
        InstanceBuilder::new("Part").with_properties(properties.clone()),
        InstanceBuilder::new("Part").with_properties(properties.clone()),
    ]));

    let output = BufWriter::new(File::create("./output.rbxm").unwrap());
    let _ = to_writer(output, &dom, dom.root().children());
}

And some rbx-util output of the result:

---
num_types: 1
num_instances: 3
chunks:
  - Inst:
      type_id: 0
      type_name: Part
      object_format: 0
      referents:
        - 0
        - 1
        - 2
  - Prop:
      type_id: 0
      prop_name: BrickColor
      prop_type: BrickColor
      values:
        - 21
        - 21
        - 21
  - Prop:
      type_id: 0
      prop_name: Color3uint8
      prop_type: Color3uint8
      values:
        - - 0
          - 0
          - 0
        - - 0
          - 0
          - 0
        - - 0
          - 0
          - 0
  - Prop:
      type_id: 0
      prop_name: Name
      prop_type: String
      values:
        - Part
        - Part
        - Part
  - Prop:
      type_id: 0
      prop_name: brickColor
      prop_type: BrickColor
      values:
        - 308
        - 308
        - 308
  - Prnt:
      version: 0
      links:
        - - 0
          - -1
        - - 1
          - -1
        - - 2
          - -1
  - End

When this file is loaded in Roblox Studio, the parts end up with the color in the brickColor chunk, which is indeed the last chunk here. When rbx-dom reads this file, brickColor and BrickColor are migrated, and again, the final value depends on ordering.

After some testing, our assumption made with property migrations is wrong. Rather than Roblox always choosing the newer of the two properties to load, they instead seem just take load whichever property is later in the file. This is simpler but it means that the way we implement migrations is incorrect and it's only worked because we've gotten lucky.

This problem is only possible by intentionally inserting these properties into the dom after deserialization, so I don't really see it as a fault in property migration (which only happens during deserialization).

The easiest option for solving this is just to not serialize properties we have a migration for. This isn't good for projects like Lune however, so we'd need a more robust solution. To me, it feels like #277 is a valid solution here [...]

I agree with all this - migrated properties should not be serialized, it's just a question of when we should change it. The situation is probably better now than it was before (when using the BrickColor property would just cause errors), but if a user manages to create these inconsistencies, they're going to be pretty confused.

I think I'll start building a prototype for proxy properties this week - it's looking pretty important for correctness!

@Dekkonot
Copy link
Member Author

This problem is only possible by intentionally inserting these properties into the dom after deserialization, so I don't really see it as a fault in property migration (which only happens during deserialization).

I meant that this is a flaw in our reasoning for property migrations: one of the core assumptions we made was that Roblox would always prefer newer properties over older ones. That turns out to be wrong.

It doesn't really impact property migrations as they stand right now, so they don't need to change. It does need to be addressed though, which is the point of this issue!

a user manages to create these inconsistencies, they're going to be pretty confused

I'm envisioning a scenario where someone merges models together in Lune and then they end up with the wrong color for all of their parts. The model would have to be catastrophically old (I think the camelCase properties might even predate the binary format) but if we added more migrations, this could happen with something else if we got unlucky with property names.

@kennethloeffler
Copy link
Member

kennethloeffler commented Jul 27, 2023

one of the core assumptions we made was that Roblox would always prefer newer properties over older ones

I just don't see how migrations rest on this assumption... corecii did mention this (and it is wrong), but it doesn't seem particularly relevant to how migrations function

I'm envisioning a scenario where someone merges models together in Lune and then they end up with the wrong color for all of their parts.

This is exactly the kind of scenario that migrations prevent. Migrated properties will never exist in the dom simply as a result of a deserialization operation, because there's no way to deserialize without doing migrations, and migrations will never insert the old property. For the behavior described in this issue to occur, migrated properties must be intentionally inserted and serialized

@kennethloeffler
Copy link
Member

Maybe as a stopgap, we could also perform migrations during serialization?

@Dekkonot
Copy link
Member Author

I was in fact just being a fool, you'll have to forgive me. After giving it a third thought, yeah, we probably don't need to worry about this for now. It is a problem in a vacuum but it's not something that's directly possible right now unless someone specifically includes e.g. both Color and BrickColor in a model using something like Lune.

That said, I think we probably should still think about it when proxy properties come, since it makes sense to.

@kennethloeffler
Copy link
Member

kennethloeffler commented Jul 28, 2023

I'm thinking we should prevent serialization of migrated properties before our next release. This will more closely match our old behavior, totally prevent the inconsistencies demonstrated in this issue, and prevent any extraneous changes in results from rojo build or Lune's serializeModel/serializePlace. We'll fail Lune/Rojo users who want to use properties like BrickColor and IgnoreGuiInset, but we can't help them without more sophisticated handling of proxy properties anyway, and migrated properties will still be canonical (albeit unserializable), solving the problem in #319.

This also has me thinking... we should probably nail down some definitions for the different terms we use in reflection like "canonical," "migrated," "alias," etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants