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

Implement property migration #268

Closed

Conversation

Corecii
Copy link
Member

@Corecii Corecii commented May 12, 2023

This is a fixed version of #253 because its git history was cursed and I didn't want to force push or anything else weird to fix it.

Preview

This PR allows us to specify migrations using the patches files and a Variant to Variant converter in the actual Rust code. This is better than having the Rust code do hard-coded checks against property names and such without being super complex to implement or think about. Writing new migrations this way is easy!

The patch files look like this:

Change:
  TextLabel:
    Font:
      Serialization:
        Type: Migrate
        Property: FontFace
        Migration: FontToFontFace

I've chosen to have migrations not be performed if the target property already exists. This matches Roblox's behavior where new properties always take priority. I figure making priority work both ways would be complex, and since backwards priority isn't known behavior of any present-day properties, it's unnecessary complexity. There's room to expand this in the future if we need to.

Why do we need this?

Two new properties -- FontFace and ScreenInsets -- are migrated from old properties. When both the old and new properties are present, Roblox always prefers the new properties.

The Roblox binary format requires that all objects of the same class have the same properties present. When mixing old models and new models into the same file using Rojo or Remodel, old models get assigned defaults for the new properties, which then take priority over the old properties.

This has two clear affects that users are actively experiencing in "preview" builds of Rojo and Remodel:

  • Mixing old and new models with UI via rojo build or Remodel scripts results in all old text UI having the Arial font.
  • Mixing old and new models with ScreenGuis via rojo build or Remodel scripts results in all old ScreenGuis losing their IgnoreGuiInset value.

The only work-around for users right now is to open their models in Roblox and re-save over them to get Roblox to do the migration. This is not a reasonable ask of a Rojo fully-managed project that may have hundreds of models.

As time goes on, more properties will get migrations like this and maintaining a fully-managed Rojo codebase will become an unreasonable task.

Implementing these migrations on the rbx-dom side is quite easy. If we don't do it, either someone else will add this feature themselves or people just won't do fully-managed Rojo due to the high risk of breakage.

Changes in this PR

  • Adds a new type of PropertySerialization, Migrate, where you specify the new property name to migrate to and the name of the migration.
  • Adds named migrations, which convert from one Variant to another Variant.
  • Changes deserializer code to respect the Migrate type.
  • Adds tests for these new migrations.
  • Adds documentation instructing how to add a new migration.

@Corecii
Copy link
Member Author

Corecii commented May 12, 2023

I'd like to give this more time and attention, but I have a lot going on right now. I feel like this code can be improved, but any PR is a good start for now, right?

@Corecii Corecii changed the title Implement migration, tests, and docs Implement property migration May 12, 2023
Copy link
Member

@Dekkonot Dekkonot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beyond needing a quick update to not update our workflows because of PR #270, looks good to me. My request to log is the only serious one; in-lining format arguments or not isn't really important but we have the ability so we may as well.

Some(_) => {
let (migration, property) = migrated_to.unwrap();
if !instance.builder.has_property(property.as_ref()) {
match perform_migration(*migration, &value) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
match perform_migration(*migration, &value) {
log::trace!("Attempting migrate property {canonical_name} to {property}");
match perform_migration(*migration, &value) {

If we're going to log when one fails, it makes sense for us to log when one starts as well.

Comment on lines +350 to +355
log::warn!(
"Failed to migrate property {} to {} because: {}",
canonical_name,
property,
e.to_string()
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
log::warn!(
"Failed to migrate property {} to {} because: {}",
canonical_name,
property,
e.to_string()
);
log::warn!(
"Failed to migrate property {canonical_name} to {property} because: {}",
e.to_string()
);

@@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rust_version: [stable, "1.63.0"]
rust_version: [stable, "1.65.0"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rust_version: [stable, "1.65.0"]
rust_version: [stable]

@@ -16,7 +16,7 @@ jobs:

strategy:
matrix:
rust_version: ["1.64.0"]
rust_version: ["1.65.0"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rust_version: ["1.65.0"]
rust_version: [stable]

@Dekkonot
Copy link
Member

...I should have thought about this beforehand but I just merged #249 which changes the format of patches a bit and swaps us over to using a polished version of generate_reflection named rbx_reflector. You'll need to use that tool in this PR instead because it'll get archived Soon(tm).

Beyond having a better UX, we now no longer need to add properties to the database, only modify their serialization. That meshes well with migrations and should hopefully make it reasonable to have those migrations live as patches.

## Roblox added a new property, but it's a migration from an existing property, and the existing property no longer loads
Sometimes Roblox migrates an existing property whose type is too constrained to a new property with a more flexible type.

Without special handling, this can cause problems for binary files because when old and new models are mixed together, the binary serializer must add the new property to the old models. Without special instruction, it'll just add the default value. This can result in weird behavior like old text UI all having the Arial font, because the default value of a new property took priority.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when old and new models are mixed together

What do you mean by "mixing" models?

## Roblox added a new property, but it's a migration from an existing property, and the existing property no longer loads
Sometimes Roblox migrates an existing property whose type is too constrained to a new property with a more flexible type.

Without special handling, this can cause problems for binary files because when old and new models are mixed together, the binary serializer must add the new property to the old models. Without special instruction, it'll just add the default value. This can result in weird behavior like old text UI all having the Arial font, because the default value of a new property took priority.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the binary serializer must add

Is this talking about rbx_binary or saving from Studio?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, migrations swap out the old property for the new one in deserialization. It doesn't actual migrate the model file, it will still have the old property. I think this would be useful information to include.

@kennethloeffler kennethloeffler mentioned this pull request Jun 13, 2023
2 tasks
Dekkonot pushed a commit that referenced this pull request Jun 15, 2023
Based on PR #268

Co-authored-by: Shae A <shae@uplift.games>
@Dekkonot
Copy link
Member

Work on this has been superseded by PR #283! Sorry that we ended up bungling this, I'll try to be more mindful of how the order things get merged in the future.

@Dekkonot Dekkonot closed this Jun 15, 2023
Dekkonot pushed a commit to Dekkonot/rbx-dom that referenced this pull request Jun 20, 2023
Based on PR rojo-rbx#268

Co-authored-by: Shae A <shae@uplift.games>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants