Skip to content

feature: Programmable game objects #2681

@pandinocoder

Description

@pandinocoder

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

  1. When an assembly is loaded, the assembly-metadata.intersect.json file will be loaded
    1. 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
    2. 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
    3. 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
    4. 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
    5. If there are no matches, the assembly loader will refuse to load the assembly
  2. If the current name or any renames are not already present in KnownAssemblyAliases they will be added
  3. The current assembly version will be populated in LatestVersion of KnownAssemblies and KnownAssemblyAliases in the row specific to the current alias
  4. All [DescriptorDefinition] annotated classes will be fetched, and will be checked just like the assemblies
    1. 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
    2. 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
    3. 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

  • This feature request is not a duplicate to the best of my knowledge.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions