-
Notifications
You must be signed in to change notification settings - Fork 382
Description
Description
Types
RenamedFromAttribute
This is used to facilitate identifying objects that were renamed or moved across versions.
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, Inherits = true, AllowMultiple = true)]
Properties:
stringName(positional, required, non-null)boolIsNamespaceQualified(optional, defaultfalse, no effect on assembly attributes)
When this is used on assemblies the IsNamespaceQualified property has no effect because it is used to determine whether or not to include the current namespace of the attribute target, and assemblies don't have one.
Examples
namespace SomeNamespace;
// This will resolve `SomeNamespace.Test` to `SomeNamespace.XYZ`
[RenamedFrom("Test")]
public class XYZ {}namespace SomeNamespace;
// This will resolve `SomeNamespace.Abc.Test` to `SomeNamespace.XYZ`
[RenamedFrom("Abc.Test")]
public class XYZ {}namespace SomeNamespace;
// This will resolve `Abc.Test` to `SomeNamespace.XYZ`
[RenamedFrom("Abc.Test", IsNamespaceQualified = true)]
public class XYZ {}DescriptorDefinitionAttribute
This is used to identify types as descriptor definitions that will be identified by the loader and instantiated.
[AttributeUsage(AttributeTargets.Class, Inherits = true, AllowMultiple = false)]
Properties:
GameObjectTypeType(positional, required, non-null) or should this be aType(e.g.typeof(EventDescriptor)?
DescriptorDefinition (class? record?, leaning towards record for with syntax)
Should we have an abstract class for code-based descriptor definitions? No examples of how this would be useful beyond some common API (can be done as an interface too though).
KnownAssembly (readonly sealed record)
[Key]GuidId(sourced fromassembly-metadata.intersect.json)stringName(most recent assembly name, should not include version or public key)stringVersion(most recent version of the assembly)[NotMapped]AssemblyAssembly(the CLR assembly reference, required for the construction of theKnownAssembly)HashSet<KnownAssemblyAlias>AliasesHashSet<KnownType>Types
KnownAssemblyAlias (readonly sealed record)
- Primary key across
IdandName GuidId[ForeignKey(nameof(Id))]KnownAssemblyAssemblystringName(assembly alias, should not include version or public key, there will always exist an alias that exactly matches theKnownAssembly)string?Version(most recent version of the renamed assembly, null if the assembly was never loaded with this alias)
KnownTypeKind (enum : byte)
Unknown=0)Descriptor=1
Enum is meant to leave room for other kinds of known types in future features.
KnownType (readonly sealed record)
GuidAssemblyId[ForeignKey(nameof(AssemblyId))]KnownAssemblyAssembly[Key]GuidId(UUID v5)stringName(this is namespace-qualified, there will always exist an alias that exactly matches theIdandName)KnownTypeKindKind[NotMapped]TypeCLRType(required for construction of theKnownType)HashSet<KnownTypeAlias>Aliases
KnownTypeAlias (readonly sealed record)
- Primary key across
TypeIdandName GuidId(UUID v5)GuidAssemblyId[ForeignKey(nameof(AssemblyId))]KnownAssemblyAssemblyGuidTypeId(UUID v5)[ForeignKey(nameof(TypeId))]KnownTypeTypestringName(this is namespace-qualified)
Source Generators
We need a source generator to generate a assembly-metadata.intersect.json file
- embedded into the
Propertiesfolder of all assemblies - if an assembly was made without it the assembly loader will refuse to load it
- the file will be referenced as an embedded resource from the
Intersect.propsandIntersectPlugin.propsfiles so each individual assembly doesn't need to add it (the source generator will also be referenced in those files for the same reason, this can get put into Intersect.Building with the key generator task) - the ID file is not a secret and must be committed with the rest of the assembly code base
Current structure:
{
"Version": 1,
"AssemblyId": "<UUIDv4 or UUIDv7>"
}Database Tables
KnownAssemblies
- Primary key is
Id Id(UUID v4 or v7), not database-generated, this is sourced fromassembly-metadata.intersect.jsonKnownAs(string, not nullable) this is the most recently seen assembly nameLatestVersion(string, not nullable) this is the most recently seen assembly version- Primary key is
Idand is unique (in this table, seeKnownAssemblyAliasesfor rename-aware aliases)
KnownAssemblyAliases
- Primary key is the union of
IdandName - (FK)
Id(UUID v4 or v7), foreign key toKnownAssemblies Name(string, not nullable) this is an assembly name either sourced from the assembly or[assembly: RenamedFrom(...)]attributes, not necessarily the latestVersion(string, not nullable) this is the most recently seen assembly version for the given alias
KnownTypes
- Primary key is
Id - (FK)
AssemblyId, foreign key toKnownAssemblies Id(UUID v5), generated using theAssemblyIdas the base andNameas the extra informationName(string), namespace-qualified most recently seen type name (not unique)
KnownTypeAliases
- Primary key is union of
Id - Unique index across
CurrentIdandName - (FK)
AssemblyId, foreign key toKnownAssemblies - (FK)
CurrentTypeId, foreign key toKnownTypes Id(UUID v5), generated using theAssemblyIdas the base and theNameof this row (not the current type name) as the extra informationName, the namespace-qualified name of the type (sourced from current type name andRenamedFromattributes)
Loader
Success/Error Handling
- No saved data will be have references processed processed until after all description definitions have loaded successfully
- If any type in an assembly fails to load, the assembly will be marked as failed
- If any assembly fails to load, the server will not load game objects, and will not start
- All "failed" types and assemblies will be dumped to the log, grouped by the assembly the types are in
Identifying Known Types
- When an assembly is loaded, the
assembly-metadata.intersect.jsonfile will be loaded- If the version listed isn't the current version the loader will refuse to load the assembly
- this is not the assembly version, it is a manifest version
KnownAssembliesis checked for a row with a matchingId, if it found the current assembly name (not version qualified) will be compared againstKnownAs- If it is not matched, it will iterate through the
[assembly: RenamedFrom(...)]attributes, and if a match to the currentKnownAsis found it will move to step 2 - If none of the
[assembly: RenamedFrom(...)]attributes match, it will check theKnownAssemblyAliasesfor a match, first with the current name and then by all of the renames -- if a match is found it will move to step 2 - If there are no matches, the assembly loader will refuse to load the assembly
- If the version listed isn't the current version the loader will refuse to load the assembly
- If the current name or any renames are not already present in
KnownAssemblyAliasesthey will be added - The current assembly version will be populated in
LatestVersionofKnownAssembliesandKnownAssemblyAliasesin the row specific to the current alias - All
[DescriptorDefinition]annotated classes will be fetched, and will be checked just like the assemblies- If when an assembly is loaded the ID is already in
KnownTypesand it does not match directly, has no matchingRenamedFromattributes, and it doesn't directly match or have anyRenamedFromoverlap inKnownTypeAliases, loading will be refused - If there's a match but the
KnownTypesentry (identified by foreign keyCurrentTypeIdinKnownTypeAliases) does not directly match, the entry will be replaced with the appropriate entry for the current type, and all aliases for the previous entry will have theirCurrentTypeIdcolumn updated to the newId- After all descriptor definitions have been successfully loaded, existing game objects for the previous type when loaded will be checked and resolved "by reference"
- Any updates will be saved after all references in their row or referenced rows has been changed in memory
- The aliases list will be updated to include all aliases referenced by
RenamedFrom
- If when an assembly is loaded the ID is already in
Intended Use-Case
Allow game objects, principally events, to be written in code for those who would prefer to not use things like the event editor.
This also facilitates forks and plugins with built-in/portable game objects.
Duplicate Check
- This feature request is not a duplicate to the best of my knowledge.