(there was a lovecraftian-type story here that I wrote, but I was left unsatisfied with it, so I removed it; if you want, you can still find it and read it through GitHub's repo commit history)
Several weeks prior to the demo, I thought it'd be fun to play a bingo game, centered around SoR2's code. I came up with a bunch of spaces, about both the good and the bad code. I announced that in SoR's Discord, and started slowly revealing the spaces on the cards. At first, one space a day, and then two spaces per day, as the demo's release date approached.
Update (30/12/24): Ignore the "Directions are enums" space being checked on the left. At the time, I was just so happy to see an enum in the code, that I didn't even look at how that enum was used. I was not aware it was possible to misuse enums that badly...
Bingo space explanations
When I was in the process of revealing bingo card spaces, I had plenty of time, so I provided detailed explanations for some of them.
Good spaces are green (left card), Bad spaces are red (right card).
-
X and Y instead of Vector2. The entire purpose of structures is to group similar and relevant data together. This way the processor can address the data much faster. Another thing that structures can do - is align the fields' padding with the processor's architecture (that's what the JIT compiler does) for more efficient access. Structures can also be copied much quicker than individual components, - that's just the value type semantics. And, of course, Vector2 is much more readable and easier to understand than just a pair of separate components.
-
Argument validation. Validating input data and throwing exceptions. Invalid state is the main cause of errors that are caused by other errors. An exception throw in a correct place (or even just a conditional statement) would stop the program, before it does anything that corrupts state. Sure, I understand that it'd be weird for a game to crash after just a single exception, but that's what exception handling is for (try-catch block).
-
Loop-switch sequence. This one has a Wikipedia article, so I'll suggest you read it instead. Examples in SoR1:
Agent.LoadDialogue()
,AgentHitbox.SetupBodyStrings()
,InvSlot.SortItems()
,InvSlot.SortUseItems()
,LevelEditor.RefreshCustomCharacters()
(x2),LevelEditor.SaveChunkData()
,LevelEditor.OpenLoadExtra()
,MouseCursorSets.SetupCursors()
(x2),PoolsScene.SetupWalls()
(x2),PoolsScene.DoInstantiate()
. -
SPANS?!?!?. Now this is quite a reach, I know. The purpose of
Span<T>
is to reduce the amount of unnecessary array/string allocations in synchronous operations. The performance improvement would be noticeable, and memory usage would go down by a lot, but that's only if all the libraries involved can handle this sort of data, and Unity doesn't know how to handle spans (or evenMemory<T>
). This is more of a "new .NET" thing, rather than a general code improvement. If one were to be looking for a .NET library for something, they'd definitely give more preference to ones that use spans. SoR doesn't have much of a "back-end" that could use this sort of improvement, so I wouldn't expect it. -
Improper list population w/ excessive copying. There is a way to populate a list very inefficiently, that involves
Insert
. And that's pretty much all there is to it. It's very inefficient, forcing the list to recopy the entire array just one item over, every time an item is added. Also, another few small, related things:Add
in a loop is much less efficient thanAddRange
, - lists are good at copying collections. And lastly, lists shouldn't be used as queues, queues should be used as queues. -
Version control (free!). A free space! We already know that Matt is using version control (Unity's kind, but version control nonetheless). Version control is an important element in development of any project, even if you're the only developer. It helps keep track of the changes you made since the last release, allows you to easily backtrack and look back at old code, makes you think in terms of features and components instead of just doing whatever works and going along with it, and makes collaboration with others easy.
-
Enormous if-chain. A lot of
if
s chained one after another (more than, like, 10). It never makes sense to have that many chainedif
s, - you either don't know what aswitch
statement is, or you messed up really badly with your program's structure. -
Directions are enums. In SoR1 directions are strings, - the most inefficient method anyone could ever think of. A beginner programmer's instinct should be to use small integers to represent directions, not strings! The overhead here is enormous even on the latest versions of .NET: comparison is 3x slower, memory usage is 2x greater (Note: interned strings in the SOH are negligible, but still...).
-
isAgent, isItem, isFire, isBullet, isObjectReal fields. Instead of type-checking an object (
is
expression), Matt first uses one of these 11 fields, defined onPlayfieldObject
, and only then casts an object to the needed type. It's completely unnecessary, as this doesn't eliminate the type-check, - the runtime still needs to be sure the object is of the correct type, and throws an exception if it isn't. These fields add an overhead of 11 bytes to each object, and don't do anything besides these unnecessarily complex type casts. It's just a bad, uninformed design decision, nothing too serious, since modders can still use type-checks in a correct way.
Festive, huh?
And that's all the explanations. I only had the time to explain 9 random spaces, before the demo dropped, and now I'm not nearly as motivated to do the rest, since I've got nothing to hype up.