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 Font type #248

Merged
merged 23 commits into from
Mar 12, 2023
Merged

Conversation

Corecii
Copy link
Member

@Corecii Corecii commented Jan 15, 2023

This PR implements the Font type:

  • Implements binary de/serializer
  • Implements xml de/serializer
  • Implements Lua/json de/serializer
  • Adds some relevant tests
  • Updates reflection database

This is my first time working with rbx-dom's code, so I may have gotten some things wrong! I also have some general questions and concerns:

  • The Font value I saw in a hex editor looked different from what rbx-dom would output. Does rbx-dom do some transformation on all binary property data to make them sense? Maybe I didn't read deep enough about it. Should I have included information about that in my documentation of the Font type?
  • The Font type has a u8 and u16 which act a lot like enums but aren't exactly enums. I made special types for these with support for an Other so that any new items should just work even if they're not nicely represented. Was this the correct move? I could have represented Weight with just a u16, but Style needs a u8 <-> name converter so that it can be serialized to/from xml.
  • The FontStyle type serializes to xml by name, but to binary as u8. I included an Other enum variant here since I figured having support for unknown new values was a good thing, but that was before I saw how xml did it. This means unknown new FontStyles can go from binary -> binary, but not binary -> xml or xml -> anything. Is this okay, or do we want to remove the Other variant here? Related: in cases where the variant is unknown, I default to Normal. Would it be better to error?
  • The default font style I have listed is unstyled Source Sans Pro. This matches the default when you make a new object in Studio, but not the default when loading a type in Studio that's missing the Font property. Studio will actually give TextLabels (and such) that are missing the Font property and unstyled Arial font. Should that be our default, or should Source Sans Pro be our default?

@Corecii
Copy link
Member Author

Corecii commented Jan 15, 2023

Oh, I just realized this is also missing conversion from the old font property to this new one, so if a very old rbxm of rbxl is loaded with rbx-dom, its FontFace will be incorrect. What's the correct way to handle that case?

Edit: I realize this isn't necessary to do. The object deserialized by rbx-dom will lack the new Font property, but that's fine because it will include the old font property which Roblox will convert to the new property fine. I was getting this confused with some other related issue caused by rbx-dom not having Font support.

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.

In a hurry at the moment so I can't do a more comprehensive review of the actual code, but here's one for the spec file at least.

It generally looks good, it just needs some changes to fit with the format we use throughout the spec files.

docs/binary.md Outdated Show resolved Hide resolved
docs/binary.md Outdated Show resolved Hide resolved
docs/binary.md Outdated Show resolved Hide resolved
Co-authored-by: Micah <dekkonot@rocketmail.com>
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.

Gave the files a proper look!

This is mostly fine, but there's some cleanup I'd like done with FontWeight and FontStyle. We'd ideally not use .into() or have to clone them, and most of my comments are related to that + cleanup from changing them.

rbx_binary/src/deserializer/state.rs Outdated Show resolved Hide resolved
rbx_binary/src/serializer/state.rs Outdated Show resolved Hide resolved
rbx_binary/src/text_deserializer.rs Outdated Show resolved Hide resolved
rbx_types/src/font.rs Outdated Show resolved Hide resolved
rbx_types/src/font.rs Outdated Show resolved Hide resolved
rbx_types/src/font.rs Show resolved Hide resolved
rbx_types/src/font.rs Show resolved Hide resolved
rbx_xml/src/types/font.rs Outdated Show resolved Hide resolved
rbx_xml/src/types/font.rs Outdated Show resolved Hide resolved
* Use explicit from_x and to_x
* Derive and use Copy
* Nicer default when writing cached_face_id
@Corecii
Copy link
Member Author

Corecii commented Jan 18, 2023

My most last commit addresses all of your comments!

@Dekkonot Dekkonot dismissed their stale review January 18, 2023 23:38

Resolved

(replaced by ScreenInsets)
@Corecii
Copy link
Member Author

Corecii commented Jan 19, 2023

Tests should pass now. It's unrelated to the font implementation -- probably related to updating the reflection database. Since IgnoreGuiInset no longer saves, it isn't present in the decoded or encoded rbxms.

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.

Looks good to me!

For anyone curious about the prior CI failure, the serialization of IgnoreGuiInserts was changed in release 554 and updating the reflection database exposed us to that change. Thus (as expected) the test involving ScreenGuis failed.

We likely should try to fix this in the future so that Roblox reflection changes don't interfere with our tests like this, but that's well outside of scope for this PR.

@Dekkonot
Copy link
Member

I meant to leave a comment a while back answering your questions but I forgor and got caught in the review sauce, so you'll have to forgive me.

The Font value I saw in a hex editor looked different from what rbx-dom would output. Does rbx-dom do some transformation on all binary property data to make them sense? Maybe I didn't read deep enough about it. Should I have included information about that in my documentation of the Font type?

Roblox serializes their binary format with compression (every chunk is LZ4 compressed), but rbx-dom doesn't at the moment. That's the only difference I can think of.

The Font type has a u8 and u16 which act a lot like enums but aren't exactly enums. I made special types for these with support for an Other so that any new items should just work even if they're not nicely represented. Was this the correct move? I could have represented Weight with just a u16, but Style needs a u8 <-> name converter so that it can be serialized to/from xml.

I considered this while I was doing the review, and eventually decided that it was a good call. It doesn't really impact things for rbx-dom either way, but using enums should make it easier for Rojo to implement a good format for Font property types.

The FontStyle type serializes to xml by name, but to binary as u8. I included an Other enum variant here since I figured having support for unknown new values was a good thing, but that was before I saw how xml did it. This means unknown new FontStyles can go from binary -> binary, but not binary -> xml or xml -> anything. Is this okay, or do we want to remove the Other variant here? Related: in cases where the variant is unknown, I default to Normal. Would it be better to error?

A younger less experienced version of me would say "we should error whenever we encounter unknown data" but as I've seen rbx-dom and Rojo get surprised by things, I'm inclined to say it's not a bad idea to maintain data when we can. The XML limit is a shame but isn't our fault because there's quite literally nothing we could do about it either way. At the very least this implementation will let binary files do what they need to do.

The default font style I have listed is unstyled Source Sans Pro. This matches the default when you make a new object in Studio, but not the default when loading a type in Studio that's missing the Font property. Studio will actually give TextLabels (and such) that are missing the Font property and unstyled Arial font. Should that be our default, or should Source Sans Pro be our default?

Anybody who cares about Font will be overriding it so it isn't a big deal and I'd rather use what Roblox's default is because it's probably what people will be expecting anyway. Not sure if this is what we do everywhere but I wouldn't be surprised if the same reasoning was used.

Copy link
Contributor

@LPGhatguy LPGhatguy left a comment

Choose a reason for hiding this comment

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

Thank you for the PR! Here are a few comments from me, then this change looks good to me.

docs/binary.md Outdated Show resolved Hide resolved
rbx_types/src/font.rs Outdated Show resolved Hide resolved
rbx_types/src/font.rs Outdated Show resolved Hide resolved
rbx_types/src/font.rs Outdated Show resolved Hide resolved
rbx_xml/src/types/font.rs Show resolved Hide resolved
rbx_xml/src/types/font.rs Outdated Show resolved Hide resolved
@LPGhatguy
Copy link
Contributor

The compatibility story for this property type might be a bit hairy. When merging older models without Font with newer models that do have the property, which property takes precedence in Studio? Will users need to re-save all of their models? Should we offer some sort of normalization or migration pass?

@Dekkonot @Corecii

Roblox serializes their binary format with compression (every chunk is LZ4 compressed), but rbx-dom doesn't at the moment. That's the only difference I can think of.

rbx_binary does output compressed chunks. The compression code is here:

pub fn dump<W: Write>(self, mut writer: W) -> io::Result<()> {
writer.write_all(self.chunk_name)?;
match self.compression {
ChunkCompression::Compressed => {
let compressed = lz4::block::compress(&self.buffer, None, false)?;
writer.write_le_u32(compressed.len() as u32)?;
writer.write_le_u32(self.buffer.len() as u32)?;
writer.write_le_u32(0)?;
writer.write_all(&compressed)?;
}
ChunkCompression::Uncompressed => {
writer.write_le_u32(0)?;
writer.write_le_u32(self.buffer.len() as u32)?;
writer.write_le_u32(0)?;
writer.write_all(&self.buffer)?;
}
}
Ok(())
}

and is referenced all over in the serializer:

let mut chunk = ChunkBuilder::new(b"SSTR", ChunkCompression::Compressed);

The default font style I have listed is unstyled Source Sans Pro. This matches the default when you make a new object in Studio, but not the default when loading a type in Studio that's missing the Font property. Studio will actually give TextLabels (and such) that are missing the Font property and unstyled Arial font. Should that be our default, or should Source Sans Pro be our default?

We should match the default value for what Roblox does for creating the instance with Instance.new, not the value when inserted via Studio's UI. That matches what happens when the Font property is unset, Arial. We shouldn't deviate from the strategy of using the instance's defaults instead of Studio's nice defaults since it can mask problems when things are broken. In any case, rbx_reflection should pick up the new property and property type and make the default value (Arial) explicit anyways.

@Dekkonot
Copy link
Member

rbx_binary does output compressed chunks. The compression code is here:

That is rather embarrassing on my part! I thought I'd looked properly and I don't really have an excuse because I did in fact know that... I literally wrote the code for serializing SSTR. 🤦🏼 Thank you for the correction. In that case, I'm not sure how to explain the difference between rbx-dom's output and Studio's beyond maybe the compression level we use.

When merging older models without Font with newer models that do have the property, which property takes precedence in Studio? Will users need to re-save all of their models? Should we offer some sort of normalization or migration pass?

Every value of the Font enum has an equivalent Font value with the exception of Unknown, which doesn't do anything when Studio reads it. This means that we can directly translate the Font enum to Font values when we have to. This would have to be done via a migration pass, but it could be done in the background because it isn't destructive.

@Corecii
Copy link
Member Author

Corecii commented Jan 21, 2023

I'm going to be away from home for the next week and may not have time to implement fixes it changes to this PR in that time. I'll get to it when I'm back, or maybe if I have some downtime while I'm gone! Just want to let you all know beforehand so you don't think I've forgotten or abandoned this PR.

@LPGhatguy
Copy link
Contributor

No rush on addressing feedback! These days I'm not around consistently. I've been working on trying to get myself out of the pipeline and find groups of maintainers for all this stuff I'm still the main blocker on.

That is rather embarrassing on my part! I thought I'd looked properly and I don't really have an excuse because I did in fact know that... I literally wrote the code for serializing SSTR. 🤦🏼 Thank you for the correction. In that case, I'm not sure how to explain the difference between rbx-dom's output and Studio's beyond maybe the compression level we use.

I figure that's probably it. There are usually many ways to compress the same data, and LZ4 is tunable.

Every value of the Font enum has an equivalent Font value with the exception of Unknown, which doesn't do anything when Studio reads it. This means that we can directly translate the Font enum to Font values when we have to. This would have to be done via a migration pass, but it could be done in the background because it isn't destructive.

This is really good news. I wonder if this is something we should plan to do in rbx_binary if we get any user reports of weird behavior. Tools like Remodel and Rojo that aggregate models might produce some funny results if we don't do anything.

@Dekkonot
Copy link
Member

This is really good news. I wonder if this is something we should plan to do in rbx_binary if we get any user reports of weird behavior. Tools like Remodel and Rojo that aggregate models might produce some funny results if we don't do anything.

If we do it in rbx_binary, we're essentially promising to do it a second time if anything like this happens again. I don't love that idea and would instead prefer we leave it up to consumers to patch files. This would let them decide how to handle it themselves and let them patch properties we don't/can't. However, if we do decide to implement it, I'd prefer we do it with a crate that wasn't rbx_binary.

The idea of a codec that modifies data in that fashion feels wrong even if the intent is to fix something Roblox did. It should be done by someone else, regardless of it it's another rbx_dom crate or an upstream user.

@NecroticNanite
Copy link

Just checking if anyone is still looking into this, and if it will be released soon?
This is going to be a big problem for us very soon if not resolved

@Corecii
Copy link
Member Author

Corecii commented Feb 12, 2023

All of your feedback should be addressed now!

In any case, rbx_reflection should pick up the new property and property type and make the default value (Arial) explicit anyways.

Does this mean the default trait does not need to be changed to reflect Roblox? Should we still change it to match?

@NecroticNanite
Copy link

Hi, can someone explain how to link our local aftman.toml file to the WIP Uplift games? I tried a variety of URL formats and didn't have much success

@Corecii
Copy link
Member Author

Corecii commented Feb 14, 2023

Hi, can someone explain how to link our local aftman.toml file to the WIP Uplift games? I tried a variety of URL formats and didn't have much success.

Our fork versions are one version ahead plus a prerelease tag. Here's what our rojo and remodel entries look like:

[tools]
rojo = "UpliftGames/rojo@7.2.2-uplift.release.4"
remodel = "UpliftGames/remodel@0.12.0-uplift.release.9"

Don't forget to switch back to rojo-rbx/rojo when the Font fixes are merged in to the main repo. The versions above are from the very first version of this PR -- which works and is compatible, but is outdated and is missing some non-critical fixes.

@NecroticNanite
Copy link

@Corecii I tested changing my aftman file to what you have above, but I still encounter the same issue where fonts are not synced
Output file has :
<Font name="FontFace"> <Family><url>rbxasset://fonts/families/GothamSSm.json</url></Family> <Weight>700</Weight> <Style>Normal</Style> </Font>
But I still get arial in studio after deleting and re-importing via Rojo

@NecroticNanite
Copy link

Sorry to bump again, but is there an update on when this might get merged? Or an answer to my above question as to why the forked version still exhibits the same issue?

Copy link
Member

@Kampfkarren Kampfkarren left a comment

Choose a reason for hiding this comment

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

Small nits

rbx_dom_lua/src/EncodedValue.lua Outdated Show resolved Hide resolved
rbx_types/src/font.rs Show resolved Hide resolved
rbx_types/src/font.rs Outdated Show resolved Hide resolved
@Corecii
Copy link
Member Author

Corecii commented Feb 28, 2023

Nits fixed!


@NecroticNanite thanks for this report! rbx_dom had been set to exclude Font types from the reflection database and I didn't catch that when implementing. Additionally, we must manually include the new version of rbx_dom in the plugin in the rojo repo. We didn't catch this at Uplift because we're not syncing in models with rojo -- we only needed updated rbx_dom for remodel. The fork should work now if you change the version to rojo = "UpliftGames/rojo@7.2.2-uplift.release.5" and install the updated plugin through either the rojo menu in vs code or the rojo plugin install command.

@NecroticNanite
Copy link

Thanks for fixing that - however, I cannot download that version :(
Aftman gives me the following: "Aftman error: Could not find release UpliftGames/rojo@7.2.2-uplift.release.5"
Installing previous versions works fine (tried .2 and .4)
I'm guessing not published somehow?

@Corecii
Copy link
Member Author

Corecii commented Mar 2, 2023

@NecroticNanite You're correct! Again, a symptom of us not needing this (yet). I had GitHub generate the release draft but didn't go press publish on it when it was done. Try now!

@NecroticNanite
Copy link

@Corecii Thanks a lot, that seems to have done the trick! Good timing too, we've got a tricky merge coming up and a release to sync to different environments :D

@NecroticNanite
Copy link

NecroticNanite commented Mar 3, 2023

@Corecii Hi, we're just testing this now.
First issue:
Old files (from probably 6-12 months ago), that have not been re-exported, will prevent Rojo starting:
The file has a Font XML as follows:

<token name="Font">9</token>
<Font name="FontFace"></Font>

It will throw the following error when trying to start Rojo:

[ERROR rojo] Malformed rbxm file: [path]/[to]\Conversation.rbxmx
        
        Caused by:
            line 139, column 27: Unexpected XML event EndElement(Font)

Not sure how easy it will be to add back-compat for this - if it's not possible, will likely make this MR go from a minor hotfix into a full major version release, as anyone using it will have to do a fresh export of any model/rbxmx files.

The fix (at least, it seems to fix it) is to delete the blank lines from any rbxmx files - possibly the parser should just ignore those empty blocks?

(We're testing a few other things, and will send any issues along as/when we hit them)

@Corecii
Copy link
Member Author

Corecii commented Mar 3, 2023

Oh that's fascinating! I'm sure we can just ignore it if it's empty. Do you happen to have any files you're willing to share which I can use to test a fix for this locally?

Edit: I realize I can easily make an example given the snippet you posted. Thanks!

I'm a little worried that there might be something similar in the binary rbxm though, and I'm not sure I have any models with this issue to test with. I'll just focus on fixing this issue with the xml for now.

@NecroticNanite
Copy link

NecroticNanite commented Mar 3, 2023

Oh that's fascinating! I'm sure we can just ignore it if it's empty. Do you happen to have any files you're willing to share which I can use to test a fix for this locally?

Conversation.rbxmx - Pastbin: https://pastebin.com/qhAUyGzP
Password: xbL1NXXtUc

@NecroticNanite
Copy link

Everything else seems to be working great though!

@Corecii Corecii mentioned this pull request Mar 12, 2023
@LPGhatguy
Copy link
Contributor

Updated the MSRV on master, should fix the CI build.

@LPGhatguy LPGhatguy dismissed Kampfkarren’s stale review March 12, 2023 21:35

Moving forward with the merge, plus follow-up changes

Copy link
Contributor

@LPGhatguy LPGhatguy left a comment

Choose a reason for hiding this comment

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

LGTM. I'm going to put in a follow-up commit to update the signatures of from_u8 and from_u16 for the sub-types of Font.

@LPGhatguy LPGhatguy merged commit 1227bfd into rojo-rbx:master Mar 12, 2023
Dekkonot pushed a commit that referenced this pull request May 22, 2023
Looks like we forgot to do this in #248
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.

5 participants