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:
string Name (positional, required, non-null)
bool IsNamespaceQualified (optional, default false, 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:
GameObjectType Type (positional, required, non-null) or should this be a Type (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] Guid Id (sourced from assembly-metadata.intersect.json)
string Name (most recent assembly name, should not include version or public key)
string Version (most recent version of the assembly)
[NotMapped] Assembly Assembly (the CLR assembly reference, required for the construction of the KnownAssembly)
HashSet<KnownAssemblyAlias> Aliases
HashSet<KnownType> Types
KnownAssemblyAlias (readonly sealed record)
- Primary key across
Id and Name
Guid Id
[ForeignKey(nameof(Id))] KnownAssembly Assembly
string Name (assembly alias, should not include version or public key, there will always exist an alias that exactly matches the KnownAssembly)
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)
Guid AssemblyId
[ForeignKey(nameof(AssemblyId))] KnownAssembly Assembly
[Key] Guid Id (UUID v5)
string Name (this is namespace-qualified, there will always exist an alias that exactly matches the Id and Name)
KnownTypeKind Kind
[NotMapped] Type CLRType (required for construction of the KnownType)
HashSet<KnownTypeAlias> Aliases
KnownTypeAlias (readonly sealed record)
- Primary key across
TypeId and Name
Guid Id (UUID v5)
Guid AssemblyId
[ForeignKey(nameof(AssemblyId))] KnownAssembly Assembly
Guid TypeId (UUID v5)
[ForeignKey(nameof(TypeId))] KnownType Type
string Name (this is namespace-qualified)
Source Generators
We need a source generator to generate a assembly-metadata.intersect.json file
- embedded into the
Properties folder 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.props and IntersectPlugin.props files 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 from assembly-metadata.intersect.json
KnownAs (string, not nullable) this is the most recently seen assembly name
LatestVersion (string, not nullable) this is the most recently seen assembly version
- Primary key is
Id and is unique (in this table, see KnownAssemblyAliases for rename-aware aliases)
KnownAssemblyAliases
- Primary key is the union of
Id and Name
- (FK)
Id (UUID v4 or v7), foreign key to KnownAssemblies
Name (string, not nullable) this is an assembly name either sourced from the assembly or [assembly: RenamedFrom(...)] attributes, not necessarily the latest
Version (string, not nullable) this is the most recently seen assembly version for the given alias
KnownTypes
- Primary key is
Id
- (FK)
AssemblyId, foreign key to KnownAssemblies
Id (UUID v5), generated using the AssemblyId as the base and Name as the extra information
Name (string), namespace-qualified most recently seen type name (not unique)
KnownTypeAliases
- Primary key is union of
Id
- Unique index across
CurrentId and Name
- (FK)
AssemblyId, foreign key to KnownAssemblies
- (FK)
CurrentTypeId, foreign key to KnownTypes
Id (UUID v5), generated using the AssemblyId as the base and the Name of this row (not the current type name) as the extra information
Name, the namespace-qualified name of the type (sourced from current type name and RenamedFrom attributes)
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.json file 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
KnownAssemblies is checked for a row with a matching Id, if it found the current assembly name (not version qualified) will be compared against KnownAs
- If it is not matched, it will iterate through the
[assembly: RenamedFrom(...)] attributes, and if a match to the current KnownAs is found it will move to step 2
- If none of the
[assembly: RenamedFrom(...)] attributes match, it will check the KnownAssemblyAliases for 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 current name or any renames are not already present in
KnownAssemblyAliases they will be added
- The current assembly version will be populated in
LatestVersion of KnownAssemblies and KnownAssemblyAliases in 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
KnownTypes and it does not match directly, has no matching RenamedFrom attributes, and it doesn't directly match or have any RenamedFrom overlap in KnownTypeAliases, loading will be refused
- If there's a match but the
KnownTypes entry (identified by foreign key CurrentTypeId in KnownTypeAliases) 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 their CurrentTypeId column updated to the new Id
- 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
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
Description
Types
RenamedFromAttributeThis 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
IsNamespaceQualifiedproperty 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
DescriptorDefinitionAttributeThis 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 towardsrecordforwithsyntax)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(readonlysealedrecord)[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>TypesKnownAssemblyAlias(readonlysealedrecord)IdandNameGuidId[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=1Enum is meant to leave room for other kinds of known types in future features.
KnownType(readonlysealedrecord)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>AliasesKnownTypeAlias(readonlysealedrecord)TypeIdandNameGuidId(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.jsonfilePropertiesfolder of all assembliesIntersect.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)Current structure:
{ "Version": 1, "AssemblyId": "<UUIDv4 or UUIDv7>" }Database Tables
KnownAssembliesIdId(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 versionIdand is unique (in this table, seeKnownAssemblyAliasesfor rename-aware aliases)KnownAssemblyAliasesIdandNameId(UUID v4 or v7), foreign key toKnownAssembliesName(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 aliasKnownTypesIdAssemblyId, foreign key toKnownAssembliesId(UUID v5), generated using theAssemblyIdas the base andNameas the extra informationName(string), namespace-qualified most recently seen type name (not unique)KnownTypeAliasesIdCurrentIdandNameAssemblyId, foreign key toKnownAssembliesCurrentTypeId, foreign key toKnownTypesId(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
Identifying Known Types
assembly-metadata.intersect.jsonfile will be loadedKnownAssembliesis checked for a row with a matchingId, if it found the current assembly name (not version qualified) will be compared againstKnownAs[assembly: RenamedFrom(...)]attributes, and if a match to the currentKnownAsis found it will move to step 2[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 2KnownAssemblyAliasesthey will be addedLatestVersionofKnownAssembliesandKnownAssemblyAliasesin the row specific to the current alias[DescriptorDefinition]annotated classes will be fetched, and will be checked just like the assembliesKnownTypesand it does not match directly, has no matchingRenamedFromattributes, and it doesn't directly match or have anyRenamedFromoverlap inKnownTypeAliases, loading will be refusedKnownTypesentry (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 newIdRenamedFromIntended 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