Name Mangling
Udon’s API consists primarily of mangled type and function names from .NET types and functions.
Types
Type mangling behaviour is publically described in Packages/com.vrchat.worlds/Integrations/UdonSharp/Editor/Compiler/Udon/CompilerUdonInterface.cs.
It’s important to keep in mind that this code is an independent reimplementation that VRChat ‘adopted’. (The ‘actual rules’ are likely handled in the wrapper module generator, which we don’t have.)
Types are best described as ‘smooshed together from parts’.
They can’t really be understood as ‘decodable’, as they’re arguably too ambiguous for that.
Instead, there’s a number of cases to follow when encoding them:
- If this is a ref or out parameter, after everything else, add a
Refsuffix.- Return types are not considered ref or out parameters.
- This may or may not be a type rule, but type resolvers account for it as if it was one, and it’s possible for this to be encoded in C#’s reflection.
- If this is an array, encode the element type, and then add an
Arraysuffix. - If this is a generic parameter, its type is called
T.- Note that this is the only way to tell from an extern signature alone if it is generic. It is somewhat easier to determine this from the type metadata in the node definitions.
- Node definition parameter information does not handle generics, and instead uses concrete types. Therefore, a generic parameter is one where the the encoded type in the extern does not match that of the node definition parameter.
- For the general case, remove all
.(namespace dots) and+(nested types) from the type name.- If this type has generic arguments (i.e. is specialized), they are encoded and appended. A quirk applies here.
There’s also a number of ‘quirks’:
- While some code will tell you
VRC.Udon.UdonBehaviouris replaced withVRC.Udon.Common.Interfaces.IUdonEventReceiver, the answer is much more complicated than that.- TLDR:
IUdonEventReceiveris the real type. - Basically, the SDK3 assemblies can’t have
UdonBehaviourbecause it lives in end-user-visible code, which is further down the dependency ladder. So they typically useIUdonEventReceiveras their proxy type. That is to say, these APIs really are referring toIUdonEventReceiver. - But, Udon-targetting languages would prefer you thought it was called
UdonBehaviour. For this reason they use various approaches to hack in this specific type alias.
- TLDR:
- As UdonSharp code helpfully points out,
SystemCollectionsGenericListTandSystemCollectionsGenericIEnumerableTspecifically are shortened to removeSystemCollectionsGeneric.- There is a subtle implication here that all uses of type parameters are hardcoded somehow. Known cases are:
TListTIEnumerableTTArray
- There is a subtle implication here that all uses of type parameters are hardcoded somehow. Known cases are:
There are very probably errors in this guide.
Externs
Externs follow a set and defined format.
We’ll use VRCSDK3DataDataDictionary.__TryGetValue__VRCSDK3DataDataToken_VRCSDK3DataDataTokenRef__SystemBoolean as an example.
The format is best described as a parse tree, which will be shown here with bullet points:
- Wrapper/Method split
- Wrapper module:
VRCSDK3DataDataDictionary: This is the name of the wrapper module, which usually, but not always, matches the Udon type name. .: This dot, of which there is always exactly one, splits the wrapper module from the method.- Qualified method:
__TryGetValue__VRCSDK3DataDataToken_VRCSDK3DataDataTokenRef__SystemBoolean: The fully-qualified method itself.- Method name:
__TryGetValue__: Method name, as-is (except for special cases), surrounded with__. - Parameters:
VRCSDK3DataDataToken_VRCSDK3DataDataTokenRef: The parameters to the function, separated with_.- Note that some types contain
_, such asTMProTMP_Dropdown. This makes externs impossible to parse without some knowledge of existing types. outandrefparameters both become theRefsuffix.- Notably, these do not include:
- The ‘this’ parameter
- Generic argument
SystemTypes
- Note that some types contain
- Return type separator:
__- If the closing method name
__would be immediately followed by the return type separator, the return type separator is missing, except for__ctor__.
- If the closing method name
- Return type:
SystemBoolean: Describes an out parameter. IfSystemVoid, no return parameter exists.
- Method name:
- Wrapper module:
After this ‘outer layer’, decoding requires some heuristics if you want to i.e. relate node definitions to their extern parameter signatures, or determine if a method is static.
The specific order of ‘hidden parameters’ is:
instance(this)- The visible parameters.
type(generic type parameter)- Return parameter (not really hidden, but easiest to handle with the same logic due to being non-existent given
SystemVoid)
A decently reliable approach appears to be to:
- Split the wrapper module and method name out using the obvious methods.
- Main parameter decoding loop, with an ‘input hopper’ string slice starting after the method name ending
__.- If the current hopper starts with
__, consume it, return the remainder as the return type, and end. - Consume a possible opening
_. - Find the longest matching type name at the start of the string. The region covered by this type name is the ‘guarded’ region.
- If there is no matching type, there is no guarded region.
- The search does not check for a closing
_. This avoids needing to account forRefor certain generics shenanigans, as the raw base type will be detected.
- Find the first
_that is not guarded. Consume everything up to it, but not including it, and add the consumed text as a parameter.- If there is no
_, this is arguably an error case. Still, return this as the return type. - Not consuming the
_is fine due to the second rule; importantly, this leaves__intact to be caught by the first rule.
- If there is no
- If the current hopper starts with
- The following attributes can be inferred by comparison with the node definition data (note the ‘node’ and ‘signature’ parameter lists):
- A function has a generic type node parameter if and only if it is
SystemType, calledtype, ofINdirection, and the function does not have a signatureSystemTypeparameter. - A function is an instance function if and only if its first node parameter is called
instanceofINdirection with a type matching the associated type. - A function has a return value if and only if its signature return type is not
SystemVoid. - The node and signature parameter lists can be exactly related to each other by these three rules, as they imply the locations of every parameter missing from the signature parameter list.
- A function has a generic type node parameter if and only if it is
This is enough information to construct nuanced bindings, supporting most syntactical amenities, except for a complete understanding of generics.
Still, generics produce observable mismatches between node and signature parameters, and there’s evidence to suggest that types with generic parameters are probably hardcoded, so this is probably solvable.
Special Method Names
The __surrounded__ method name can have two special values:
__ctor__: Constructors (akanew Example(...)).__op_Equality__(and similar): The corresponding operators. There’s a few of these, and I don’t have a full list.- There appears to either be some confusion between
LogicalandBitwiseoperators, or C#’s implementation of the operators is unusual somehow.
- There appears to either be some confusion between