Mike amanfo
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    2
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # O3DESharp TDD **Version:** 0.2-Draft **Original Author Date:** 01-05-2026 **Authors:** Scrumpy [WD Studios], O3DE Contributors **Status:** Draft - RV1 --- ## Table of Contents 1. [Executive Summary](#1-executive-summary) 2. [Goals and Non-Goals](#2-goals-and-non-goals) 3. [System Architecture](#3-system-architecture) 4. [BehaviorContext Reflection System](#4-behaviorcontext-reflection-system) 5. [C# Binding Generation Pipeline](#5-c-binding-generation-pipeline) 6. [Assembly Management](#6-assembly-management) 7. [Threading Model](#7-threading-model) 8. [Script Execution Model](#8-script-execution-model) 9. [Developer Experience](#9-developer-experience) 10. [Hot Reload System](#10-hot-reload-system) 11. [Performance Considerations](#11-performance-considerations) 12. [Security Considerations](#12-security-considerations) 13. [Platform Support](#13-platform-support) 14. [Future Considerations](#14-future-considerations) 15. [Appendices](#15-appendices) --- ## 1. Executive Summary O3DESharp is a Gem that adds C# scripting support to the Open 3D Engine (O3DE) using the [Coral](https://github.com/WatchDogStudios/Coral) .NET host library. This TDD is mainly meant for refining design details as the community helps develop consideration/effort for the project. ### 1.2 TDD Notice Everything you see in this document **IS SUBJECT TO CHANGE.** Do not take details written in this document as **FINAL.** ### Key Design Decisions 1. **BehaviorContext as Single Source of Truth**: All EBus and class reflection data comes exclusively from O3DE's BehaviorContext. This ensures consistency with Lua scripting and avoids maintaining separate binding definitions. 2. **Two-Assembly Model**: - **Engine Core DLL**: Contains bindings for core O3DE types (AzCore, AzFramework, math types, entity system) - **Project Gems DLL**: A monolithic assembly containing bindings for all active project gems (See Section 14 for future per-gem assembly considerations). 3. **Build-Time Generation**: C# bindings are generated at build time (not runtime) to ensure type safety and IDE support. 4. **Threading-Aware Design**: The system explicitly handles threading concerns for both module loading and script execution. --- ## 2. Goals and Non-Goals ### Goals - **G1**: Provide strongly-typed C# access to any type reflected in BehaviorContext - **G2**: Generate IntelliSense-compatible C# code for excellent developer UX - **G3**: Support hot-reload of user scripts during development - **G4**: Maintain thread safety for all script operations - **G5**: Enable per-gem organization of generated bindings - **G6**: Support both Editor and runtime execution - **G7**: Minimize runtime overhead for script execution ### Non-Goals - **NG1**: Runtime code generation (all bindings are pre-generated) - **NG2**: Full C++/CLI interop (we use P/Invoke style calls via Coral) - **NG3**: Support for .NET Framework (only .NET 8.0+) - **NG4**: Automatic binding of non-BehaviorContext reflected types - **NG5**: C# to C++ inheritance (C# classes cannot derive from C++ classes) --- ## 3. System Architecture ### 3.1 High-Level Architecture ```mermaid graph TD subgraph Application [O3DE Application] subgraph Gem [O3DESharp Gem C++] BC[BehaviorContext<br/>Reflector] --> RD[ReflectionData<br/>Exporter JSON] RD --> PBG[Python Binding Generator<br/>- gem_dependency_resolver.py<br/>- csharp_binding_generator.py<br/>- generate_bindings.py] PBG --> GCS[Generated C# Source<br/>- O3DE.Core.dll Engine Types<br/>- O3DE.Gems.dll All Gem Types] CHM[CoralHostManager<br/>- .NET Runtime<br/>- Assembly Loading<br/>- Thread Dispatch] CHM --> GCS CHM --> SEL subgraph SEL [Script Execution Layer] direction LR SB[ScriptBindings<br/>Native Calls] GD[GenericDispatcher<br/>Dynamic Calls] CSC[CSharpScriptComponent<br/>Entity Scripts] end GCS --- SEL end end subgraph Coral [Coral Library] CL[HostFXR Wrapper<br/>Assembly Loading/Unloading<br/>Internal Call Registration<br/>Managed Object Lifetime] end subgraph DotNet [.NET 8.0 Runtime] Runtime[.NET 8.0] end subgraph UserScripts [User Game Scripts] US[References O3DE.Core.dll and O3DE.Gems.dll<br/>ScriptComponent-derived classes] end Application ==> Coral Coral ==> DotNet DotNet ==> UserScripts ``` ### 3.2 Component Responsibilities | Component | Responsibility | |-----------|---------------| | **BehaviorContextReflector** | Extracts metadata from O3DE's BehaviorContext at runtime | | **ReflectionDataExporter** | Serializes reflection metadata to JSON for Python consumption | | **GemDependencyResolver** | Discovers gems and resolves dependency ordering | | **CSharpBindingGenerator** | Generates C# source files from reflection JSON | | **CoralHostManager** | Manages .NET runtime lifecycle and assembly loading | | **ScriptBindings** | Implements native method callbacks for C# internal calls | | **GenericDispatcher** | Enables dynamic method invocation for BehaviorContext types | | **CSharpScriptComponent** | O3DE component that hosts C# script instances | --- ## 4. BehaviorContext Reflection System ### 4.1 Why BehaviorContext? BehaviorContext is the **only** ["reliable" source](https://www.o3de.org/docs/user-guide/programming/components/reflection/behavior-context/) for discovering reflected EBuses, classes, and methods in O3DE. This is by design: 1. **Consistency**: Same types available to Lua are available to C# 2. **Official API**: BehaviorContext is the supported scripting interface 3. **Completeness**: Includes methods, properties, EBuses, and global functions 4. **Attributes**: Contains documentation, categories, and deprecation info ### 4.2 Reflection Data Model ```cpp // Core reflection structures struct ReflectedParameter { AZStd::string name; AZ::Uuid typeId; AZStd::string typeName; bool isPointer; bool isReference; bool isConst; MarshalType marshalType; // Hint for C# marshalling }; struct ReflectedMethod { AZStd::string name; AZStd::string className; bool isStatic; bool isConst; ReflectedParameter returnType; AZStd::vector<ReflectedParameter> parameters; AZStd::string description; AZStd::string category; bool isDeprecated; }; struct ReflectedClass { AZStd::string name; AZ::Uuid typeId; AZStd::vector<AZStd::string> baseClasses; AZStd::vector<ReflectedMethod> methods; AZStd::vector<ReflectedProperty> properties; AZStd::vector<ReflectedMethod> constructors; AZStd::string description; AZStd::string category; AZStd::string sourceGemName; // Which gem this class belongs to }; struct ReflectedEBus { AZStd::string name; AZ::Uuid typeId; ReflectedParameter addressType; AZStd::vector<ReflectedEBusEvent> events; AZStd::string description; AZStd::string category; AZStd::string sourceGemName; }; ``` ### 4.3 Class-to-Gem Mapping Since BehaviorContext doesn't inherently track which gem registered a class, we use heuristics: 1. **Category Attribute**: Many classes have categories like "Atom/Rendering" 2. **Name Prefixes**: Classes often have gem-specific prefixes (e.g., "PhysX") 3. **Explicit Mapping**: Configuration file for edge cases 4. **Default Fallback**: Unmapped classes go to "O3DE.Core" ```python # Default prefix-to-gem mappings DEFAULT_PREFIX_MAPPINGS = { "AZ": "AzCore", "Atom": "Atom", "PhysX": "PhysX", "ScriptCanvas": "ScriptCanvas", "Multiplayer": "Multiplayer", # ... etc } ``` --- ## 5. C# Binding Generation Pipeline ### 5.1 Pipeline Overview ```mermaid graph LR subgraph CppRuntime [C++ Runtime] BC[BehaviorContext<br/>Reflector] end subgraph JSONExport [JSON Export] JSON[reflection.json] end subgraph PythonScripts [Python Scripts] PY[generate_*.py] end subgraph GeneratedCS [Generated C#] CSFile[*.cs files<br/>*.csproj files] end subgraph Build [dotnet build] MSBuild[MSBuild] end subgraph DLLs [Compiled DLLs] DLL[O3DE.Core.dll<br/>O3DE.Gems.dll] end BC --> JSON JSON --> PY PY --> CSFile CSFile --> MSBuild MSBuild --> DLL ``` ### 5.2 Generation Phases #### Phase 1: Reflection Export (C++) ```cpp // Triggered at Editor startup or via console command void ExportReflectionData() { AZ::BehaviorContext* context = GetBehaviorContext(); BehaviorContextReflector reflector; reflector.ReflectFromContext(context); // Resolve gem sources for organization GemDependencyResolver gemResolver; gemResolver.DiscoverGems(settingsRegistry); // Update class gem sources for (const auto& className : reflector.GetClassNames()) { AZStd::string gemName = gemResolver.ResolveGemForClass( className, reflector.GetClass(className)->category); reflector.SetClassGemSource(className, gemName); } // Export to JSON ReflectionDataExporter exporter; exporter.ExportToFile(reflector, "reflection_data.json"); } ``` #### Phase 2: Binding Generation (Python) ```bash python generate_bindings.py \ --reflection-data reflection_data.json \ --project /path/to/project \ --output Generated/CSharp ``` The Python generator: 1. Loads reflection JSON 2. Discovers gems and their dependencies 3. Groups classes by source gem 4. Generates C# source files with proper namespacing 5. Generates .csproj and .sln files #### Phase 3: Compilation ```bash cd Generated/CSharp dotnet build -c Release ``` Output: - `O3DE.Core.dll` - Core engine bindings - `O3DE.Gems.dll` - All gem bindings in one assembly ### 5.3 Generated Code Structure (PoC Structure) ``` Generated/CSharp/ ├── O3DE.Generated.sln ├── Core/ │ ├── O3DE.Core.csproj │ ├── AssemblyInfo.cs │ ├── Math.cs # Vector3, Quaternion, Transform, Matrix │ ├── Entity.cs # Entity, EntityId, Component base │ ├── Core.cs # Debug, Time, Application │ ├── Core.EBus.cs # TransformBus, EntityBus, TickBus │ └── InternalCalls.cs # [InternalCall] declarations ├── Gems/ │ ├── O3DE.Gems.csproj │ ├── Atom/ │ │ ├── Rendering.cs │ │ ├── Materials.cs │ │ └── Rendering.EBus.cs │ ├── PhysX/ │ │ ├── RigidBody.cs │ │ ├── Collision.cs │ │ └── Physics.EBus.cs │ └── ... (other gems) └── InternalCalls.cs # Shared native method declarations ``` ### 5.4 Generated Class Example ```csharp // Generated from BehaviorContext reflection namespace O3DE.Generated.PhysX { /// <summary> /// Represents a rigid body physics component. /// </summary> public partial class RigidBody : NativeObject { /// <summary> /// Gets or sets the linear velocity of the rigid body. /// </summary> public Vector3 LinearVelocity { get => NativeMethods.RigidBody_GetLinearVelocity(_nativeHandle); set => NativeMethods.RigidBody_SetLinearVelocity(_nativeHandle, value); } /// <summary> /// Applies a force to the rigid body at its center of mass. /// </summary> /// <param name="force">The force vector to apply.</param> public void ApplyLinearImpulse(Vector3 force) { NativeMethods.RigidBody_ApplyLinearImpulse(_nativeHandle, force); } } } // Internal calls (separate file) namespace O3DE.Generated.Internal { internal static class NativeMethods { [MethodImpl(MethodImplOptions.InternalCall)] internal static extern Vector3 RigidBody_GetLinearVelocity(IntPtr instance); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void RigidBody_SetLinearVelocity(IntPtr instance, Vector3 value); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void RigidBody_ApplyLinearImpulse(IntPtr instance, Vector3 force); } } ``` --- ## 6. Assembly Management ### 6.1 Assembly Architecture ```mermaid graph TD subgraph UserAssembly [User Game Assembly<br/>MyGame.Scripts.dll] UA[User ScriptComponents<br/>Game Logic<br/>Refs Core & Gems] end subgraph GemsAssembly [O3DE.Gems.dll<br/>Monolithic Gem Bindings] GA[All Gem Types<br/>Namespaced by Gem<br/>Refs Core] end subgraph CoreAssembly [O3DE.Core.dll<br/>Engine Core Bindings] CA[Math Types, Entity, Component<br/>Core EBuses<br/>Debug, Time, App<br/>Internal Calls] end subgraph CoralAssembly [Coral.Managed.dll<br/>Coral Runtime Support] CMA[ManagedObject Base<br/>Internal Call Infra<br/>Interop Utilities] end UserAssembly --> GemsAssembly UserAssembly --> CoreAssembly GemsAssembly --> CoreAssembly CoreAssembly ~~~ CoralAssembly ``` ### 6.2 Why Monolithic Gem Assembly? We chose a single `O3DE.Gems.dll` instead of per-gem assemblies for several reasons: | Factor | Per-Gem DLLs | Monolithic DLL | |--------|--------------|----------------| | **Load Time** | Slower (many files) | Faster (one file) | | **Cross-Gem References** | Complex dependencies | Simple | | **Hot Reload** | Individual gem reload | Full gem reload | | **User Simplicity** | Many references | Two references | | **Build Complexity** | Higher | Lower | **Decision**: Use monolithic gem assembly for simplicity. Per-gem assemblies can be added later as an optional mode. ### 6.3 Assembly Loading Sequence ```mermaid graph TD Startup[Application Startup] subgraph Init [1. Initialize Coral Host] S1[CoralHostManager::Initialize<br/>- Locate .NET runtime<br/>- Initialize HostFXR<br/>- Load Coral.Managed.dll] end subgraph LoadCore [2. Load Core Assemblies] S2[LoadAssembly O3DE.Core.dll<br/>LoadAssembly O3DE.Gems.dll<br/>- Must happen on main thread<br/>- Register internal calls] end subgraph RegNative [3. Register Native Methods] S3[RegisterInternalCalls<br/>- Map C# InternalCall to C++ functions<br/>- Must complete before any script] end subgraph LoadUser [4. Load User Assemblies] S4[LoadAssembly UserGameScripts.dll<br/>- Can be triggered on-demand<br/>- Supports hot-reload in Editor] end Ready[5. Ready for Script Execution] Startup --> Init Init --> LoadCore LoadCore --> RegNative RegNative --> LoadUser LoadUser --> Ready ``` --- ## 7. Threading Model ### 7.1 Threading Challenges C# scripting in a game engine presents several threading challenges: 1. **Assembly Loading**: Must happen on the thread that owns the .NET runtime 2. **Script Callbacks**: OnUpdate, OnCreate must be called from appropriate threads 3. **Native Calls**: C# calling into C++ must respect O3DE's threading model 4. **EBus Calls**: Many EBuses are main-thread only 5. **Hot Reload**: Assembly unload/reload requires thread coordination ### 7.2 Thread Ownership Model ```mermaid graph TD subgraph MainThread [MAIN THREAD<br/>Owns: .NET Runtime, Assembly Loading, Most EBuses] CHM[CoralHostManager<br/>- All Coral API calls<br/>- Assembly loading/unloading<br/>- ManagedObject creation/destruction<br/>- Internal call invocation] SLC[Script Lifecycle Callbacks<br/>- OnCreate<br/>- OnDestroy<br/>- OnUpdate] EBus[EBus Operations<br/>- Most EBus broadcasts/events<br/>- TransformBus, EntityBus, etc.] end subgraph JobThreads [JOB THREADS<br/>Physics, Asset Loading, Networking, etc.] NoExec[NO DIRECT SCRIPT EXECUTION] JobLogic[If job needs to call C# code:<br/>1. Queue a callback to main thread<br/>2. Main thread executes the C# code<br/>3. Result is communicated back if needed] end JobThreads -.->|Queue Callback| MainThread ``` ### 7.3 Thread Dispatch System ```cpp class ScriptThreadDispatcher { public: // Queue a script operation to run on the main thread using ScriptOperation = AZStd::function<void()>; void QueueOnMainThread(ScriptOperation operation); // Process all queued operations (called from main thread tick) void ProcessQueue(); // Check if current thread is the script thread (main thread) bool IsScriptThread() const; // Assert we're on the script thread void AssertScriptThread() const; private: AZStd::mutex m_queueMutex; AZStd::queue<ScriptOperation> m_pendingOperations; AZStd::thread::id m_mainThreadId; }; // Usage in CSharpScriptComponent void CSharpScriptComponent::OnTick(float deltaTime, AZ::ScriptTimePoint) { // Already on main thread, safe to call scripts AZ_Assert(m_dispatcher.IsScriptThread(), "OnTick must be on main thread"); if (m_managedObject) { m_managedObject->InvokeMethod("OnUpdate", deltaTime); } } // Usage from a job thread void PhysicsCallback::OnCollision(const CollisionEvent& event) { // NOT on main thread - must queue m_dispatcher.QueueOnMainThread([this, event]() { if (m_managedObject) { m_managedObject->InvokeMethod("OnCollision", event.ToManaged()); } }); } ``` ### 7.4 Assembly Loading Thread Safety ```cpp class CoralHostManager { public: // All assembly operations must happen on main thread void LoadAssembly(const AZStd::string& path) { AssertMainThread(); AZStd::lock_guard<AZStd::mutex> lock(m_assemblyMutex); // Load via Coral Coral::ManagedAssembly* assembly = m_hostInstance->LoadAssembly(path); if (assembly) { m_loadedAssemblies[path] = assembly; RegisterInternalCallsForAssembly(assembly); } } void UnloadAssembly(const AZStd::string& path) { AssertMainThread(); AZStd::lock_guard<AZStd::mutex> lock(m_assemblyMutex); // Must destroy all ManagedObjects first DestroyManagedObjectsForAssembly(path); // Then unload m_hostInstance->UnloadAssembly(m_loadedAssemblies[path]); m_loadedAssemblies.erase(path); } private: void AssertMainThread() { AZ_Assert(AZStd::this_thread::get_id() == m_mainThreadId, "Coral operations must be on main thread"); } AZStd::mutex m_assemblyMutex; AZStd::thread::id m_mainThreadId; AZStd::unordered_map<AZStd::string, Coral::ManagedAssembly*> m_loadedAssemblies; }; ``` ### 7.5 Thread Safety Guidelines | Operation | Thread | Synchronization | |-----------|--------|-----------------| | Assembly Load/Unload | Main only | Mutex | | ManagedObject Create/Destroy | Main only | Mutex | | Script method invocation | Main only | None needed | | Internal call execution | Main thread context | None needed | | EBus broadcast from C# | Main only (enforced) | None needed | | Physics callbacks | Queue to main | Thread dispatch | | Network callbacks | Queue to main | Thread dispatch | | Asset load callbacks | Queue to main | Thread dispatch | --- ## 8. Script Execution Model ### 8.1 Script Component Lifecycle ```mermaid graph TD Activate[Entity Activation] subgraph ActivateState [CSharpScriptComponent::Activate] S1[1. Resolve script class from ScriptAsset] S2[2. Create ManagedObject instance via Coral] S3[3. Store native entity handle in managed object] S4[4. Call managed OnCreate] S5[5. Connect to TickBus for updates] end subgraph ActiveState [Active State] FrameLoop[Every frame:<br/>- TickBus::OnTick triggers<br/>- Call managed OnUpdate deltaTime] EventLoop[On events:<br/>- Physics callbacks -> OnCollision<br/>- Input callbacks -> OnInput<br/>- Custom EBus -> OnCustomEvent] end subgraph DeactivateState [CSharpScriptComponent::Deactivate] D1[1. Disconnect from TickBus] D2[2. Call managed OnDestroy] D3[3. Release ManagedObject reference] D4[4. Coral handles GC cleanup] end Activate --> ActivateState ActivateState -.->|Result| ActiveState ActiveState -.->|Deactivate| DeactivateState S1 --> S2 S2 --> S3 S3 --> S4 S4 --> S5 D1 --> D2 D2 --> D3 D3 --> D4 ``` ### 8.2 Internal Call Implementation C# methods marked with `[InternalCall]` are bound to C++ functions: ```cpp // C++ side - Native method registration void ScriptBindings::RegisterInternalCalls() { // Entity methods Coral::InternalCall::Register( "O3DE.Core.Entity::GetName", &ScriptBindings::Entity_GetName); Coral::InternalCall::Register( "O3DE.Core.Transform::GetWorldPosition", &ScriptBindings::Transform_GetWorldPosition); Coral::InternalCall::Register( "O3DE.Core.Transform::SetWorldPosition", &ScriptBindings::Transform_SetWorldPosition); // EBus methods Coral::InternalCall::Register( "O3DE.Core.EBus.TransformBus::GetWorldPosition", &ScriptBindings::TransformBus_GetWorldPosition); } // Implementation example Coral::String32 ScriptBindings::Entity_GetName(AZ::EntityId entityId) { AZStd::string name; AZ::ComponentApplicationBus::BroadcastResult( name, &AZ::ComponentApplicationRequests::GetEntityName, entityId); return Coral::String32(name.c_str()); } AZ::Vector3 ScriptBindings::Transform_GetWorldPosition(AZ::EntityId entityId) { AZ::Vector3 position = AZ::Vector3::CreateZero(); AZ::TransformBus::EventResult( position, entityId, &AZ::TransformBus::Events::GetWorldTranslation); return position; } ``` ### 8.3 EBus Access Patterns ```csharp // C# user code - Two approaches // Approach 1: Strongly-typed generated wrapper (recommended) public class PlayerController : ScriptComponent { public override void OnUpdate(float deltaTime) { // Uses generated TransformBus wrapper Vector3 position = TransformBus.GetWorldPosition(EntityId); position += new Vector3(1, 0, 0) * deltaTime; TransformBus.SetWorldPosition(EntityId, position); } } // Approach 2: Dynamic reflection (for uncommon buses) public class DynamicExample : ScriptComponent { public override void OnUpdate(float deltaTime) { // Uses reflection system for any BehaviorContext-reflected EBus NativeReflection.SendEBusEvent( "SomeCustomBus", "SomeEvent", EntityId, 42.0f); } } ``` --- ## 9. Developer Experience ### 9.1 The Problem Without generated bindings, developers would need to: 1. Know the exact EBus name as a string 2. Know the exact method name as a string 3. Know the parameter types and order 4. Have no IntelliSense or compile-time checking 5. Discover APIs through documentation only ```csharp // BAD: Without bindings - error-prone, no IntelliSense NativeReflection.SendEBusEvent("TransfromBus", "SetWordPosition", entityId, position); // ^ typo ^ typo - runtime errors! ``` ### 9.2 The Solution With generated bindings: 1. Full IntelliSense support 2. Compile-time type checking 3. XML documentation from BehaviorContext 4. Familiar object-oriented patterns ```csharp // GOOD: With generated bindings - type-safe, discoverable TransformBus.SetWorldPosition(entityId, position); // IntelliSense shows all available methods! ``` ### 9.3 Development Workflow ```mermaid graph TD subgraph Setup [1. Project Setup One Time] Cmd1[$ cd YourProject] Cmd2[$ python Scripts/generate_bindings.py --project .] Cmd3[$ cd Generated/CSharp && dotnet build] Cmd4[$ # Add reference to generated DLLs in your script project] end subgraph Write [2. Write Scripts Daily Work] Code["// MyPlayerController.cs<br/>using O3DE.Core;<br/>using O3DE.Generated.PhysX;<br/>class MyScript : ScriptComponent<br/>..."] end subgraph Build [3. Build and Test] B1[$ dotnet build] B2[$ # Editor hot-reloads automatically] B3[$ # Test in Editor play mode] end subgraph Regen [4. Regenerate When Gems Change] R1[# When adding/removing gems<br/># When Engine updates<br/># When Custom gem adds types] R2[$ python Scripts/generate_bindings.py] end Setup --> Write Write --> Build Build --> Regen Regen --> Write ``` ### 9.4 Editor Integration The Editor-side of the the gem *will need to provide*: 1. **Script Component Inspector**: - Dropdown to select script class - Exposed public properties editable in UI 2. **Console Commands**: ``` o3desharp_generate_bindings - Regenerate all bindings o3desharp_reload_scripts - Hot-reload user assemblies o3desharp_list_scripts - List available script classes ``` 3. **Asset Processor Integration**: - Detects changes to .cs files - Triggers recompilation - Notifies Editor for hot-reload --- ## 10. Hot Reload System ### 10.1 Hot Reload Scope | What Can Hot Reload | What Cannot | |---------------------|-------------| | User script assemblies | Generated binding assemblies | | Script logic changes | New internal calls | | New script classes | Structural changes to ScriptComponent | | Property value changes | Changes to referenced types | ### 10.2 Hot Reload Sequence ```mermaid graph TD S1["Step 1: Detect Assembly Change<br/>File watcher detects: MyGame.Scripts.dll changed"] S2["Step 2: Pause Script Execution<br/>• Stop calling OnUpdate for all scripts<br/>• Queue any pending events"] S3["Step 3: Serialize Script State<br/>For each active CSharpScriptComponent:<br/>• Call managed OnBeforeReload<br/>• Serialize public field values<br/>• Store entity reference"] S4["Step 4: Unload Old Assembly<br/>• Destroy all ManagedObjects from old assembly<br/>• Call Coral::UnloadAssembly<br/>• GC.Collect to clean up"] S5["Step 5: Load New Assembly<br/>• Call Coral::LoadAssembly with new path<br/>• Verify assembly loaded successfully"] S6["Step 6: Recreate Script Instances<br/>For each CSharpScriptComponent:<br/>• Create new ManagedObject from new assembly<br/>• Deserialize saved field values<br/>• Call managed OnAfterReload"] S7["Step 7: Resume Script Execution<br/>• Resume OnUpdate calls<br/>• Process queued events"] S1 --> S2 S2 --> S3 S3 --> S4 S4 --> S5 S5 --> S6 S6 --> S7 ``` ### 10.3 State Serialization ```csharp // User's script with serializable state public class EnemyAI : ScriptComponent { // Public fields are automatically serialized during reload public float Health = 100f; public int PatrolIndex = 0; public Vector3 TargetPosition; // Private fields with attribute can opt-in [SerializeField] private float _attackCooldown; // Transient fields are not serialized (reset on reload) [NonSerialized] private object _cachedPathfinder; // Called before hot reload - save complex state public override void OnBeforeReload() { // Custom serialization for complex objects if needed } // Called after hot reload - restore complex state public override void OnAfterReload() { // Recreate transient objects _cachedPathfinder = FindPathfinder(); } } ``` --- ## 11. Performance Considerations ### 11.1 Performance Targets | Metric | Target | Notes | |--------|--------|-------| | Script creation | < 1ms | Per ManagedObject | | OnUpdate overhead | < 10μs | Per script per frame | | Internal call overhead | < 1μs | Per call | | Hot reload total | < 500ms | For typical project | | Memory per script | < 1KB | Managed side only | ### 11.2 Optimization Strategies #### Batched Updates ```cpp // Instead of calling each script individually void ScriptSystem::OnTick(float deltaTime) { // Batch all script updates into single managed call // Reduces managed/native transition overhead m_scriptBatcher.BeginBatch(); for (auto& script : m_activeScripts) { m_scriptBatcher.QueueUpdate(script.GetManagedObject(), deltaTime); } // Single transition into managed code m_scriptBatcher.ExecuteBatch(); } ``` #### Cached Method Handles ```cpp // Cache method lookups class CSharpScriptComponent { // Resolved once, reused every frame Coral::ManagedMethod* m_onUpdateMethod = nullptr; Coral::ManagedMethod* m_onCreateMethod = nullptr; void CacheMethodHandles() { if (m_managedObject) { m_onUpdateMethod = m_managedObject->GetMethod("OnUpdate", 1); m_onCreateMethod = m_managedObject->GetMethod("OnCreate", 0); } } }; ``` #### Lazy Loading ```cpp // Don't create ManagedObjects until entity activates void CSharpScriptComponent::Activate() { // Only now create the managed instance CreateManagedObject(); } ``` ### 11.3 Memory Management - **Object Pooling**: Reuse ManagedObject wrappers where possible - **Weak References**: Use for cached script references - **Explicit Disposal**: IDisposable pattern for native resources - **GC Pressure Monitoring**: Track allocations in hot paths --- ### 11.4 AOT Support Considerations --- ## 12. Security Considerations ### 12.1 Sandboxing C# scripts run with full trust in the .NET runtime. For shipped games: 1. **Code Signing**: Sign game assemblies to prevent tampering 2. **No Runtime Compilation**: All scripts are pre-compiled 3. **Limited Reflection**: Generated bindings only expose safe APIs ### 12.2 Sensitive APIs Certain operations should be restricted or audited: | API Category | Restriction | |--------------|-------------| | File System | Sandboxed to project directories | | Network | Logged, rate-limited in Editor | | Process Execution | Disabled by default | | Reflection | Limited to game assemblies | --- ## 13. Platform Support ### 13.1 Supported Platforms | Platform | .NET Runtime | Status | |----------|--------------|--------| | Windows x64 | .NET 8.0 | Full support | | Linux x64 | .NET 8.0 | Full support | | macOS (Apple Silicon) | .NET 8.0 | Planned | | Android | Mono/NativeAOT | Future consideration | | iOS | NativeAOT | Future consideration | | Consoles | TBD | Requires AOT compilation | ### 13.2 Platform-Specific Considerations #### Windows/Linux (Development Platforms) - Full JIT compilation - Hot reload supported - Debug symbols available #### Console Platforms (Future) - Requires Ahead-of-Time (AOT) compilation - No hot reload in production - Reduced reflection capabilities --- ## 14. Future Considerations ### 14.1 Potential Enhancements 1. **Visual Scripting Bridge**: Generate ScriptCanvas nodes from C# scripts 2. **Debugger Integration**: VS/Rider debugging of live scripts 3. **Profiler Integration**: C# script profiling in O3DE profiler 4. **Asset References**: Proper C# types for O3DE asset references 5. **Editor Scripting**: C# scripts for Editor tools and automation 6. **Per-Gem Assemblies**: Optional mode for large projects 7. **Source Generators**: C# source generators for compile-time binding 8. **Coral Upgrades**: Extend Coral for better Multithreading support, improved GC performance, and better error handling. ### 14.2 Technical Debt Tracking | Item | Priority | Effort | Notes | |------|----------|--------|-------| | Improve class-to-gem mapping accuracy | Medium | Medium | Machine learning on code patterns? | | Reduce hot reload time | Medium | High | Incremental reload | | Add more marshal types | Low | Low | As needed | | Console platform support | Low | High | Requires AOT | --- ## 15. Appendices ### 15.1 Glossary | Term | Definition | |------|------------| | **BehaviorContext** | O3DE's runtime reflection system for scripting | | **Coral** | .NET host library used for C# integration | | **EBus** | O3DE's event bus system for decoupled communication | | **Internal Call** | P/Invoke-style call from C# to native code | | **ManagedObject** | Coral wrapper for a C# object instance | | **Hot Reload** | Replacing running code without restarting | ### 15.2 File Structure ``` Gems/O3DESharp/ ├── Code/ │ ├── Source/ │ │ ├── Scripting/ │ │ │ ├── Reflection/ │ │ │ │ ├── BehaviorContextReflector.h/cpp │ │ │ │ └── GenericDispatcher.h/cpp │ │ │ ├── BindingGenerator/ │ │ │ │ ├── CSharpBindingGenerator.h/cpp # C++ (unused at runtime) │ │ │ │ ├── GemDependencyResolver.h/cpp # C++ (unused at runtime) │ │ │ │ └── ReflectionDataExporter.h/cpp # Exports JSON │ │ │ ├── CoralHostManager.h/cpp │ │ │ ├── ScriptBindings.h/cpp │ │ │ └── CSharpScriptComponent.h/cpp │ │ └── ... │ └── CMakeLists.txt ├── Editor/ │ └── Scripts/ │ ├── __init__.py │ ├── gem_dependency_resolver.py │ ├── csharp_binding_generator.py │ └── generate_bindings.py ├── Assets/ │ └── Scripts/ │ └── O3DE.Core/ # Core C# runtime library │ ├── O3DE.Core.csproj │ ├── ScriptComponent.cs │ ├── Entity.cs │ └── ... ├── Docs/ │ └── TechnicalDesignDocument.md # This document ├── gem.json └── README.md ``` ### 15.3 References - [Coral GitHub Repository (WD Studios Fork)](https://github.com/WatchDogStudios/Coral) - [O3DE BehaviorContext Documentation](https://www.o3de.org/docs/user-guide/programming/components/reflection/behavior-context/) - [.NET Hosting APIs](https://docs.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting) - [O3DE EBus Documentation](https://www.o3de.org/docs/user-guide/programming/messaging/ebus/) --- ### Document Editors | User | GitHub | Contact Email | | -------- | -------- | -------- | | Scrumpy [WD Studios] | JailbreakPapa | mikaelamanfo@wdstudios.tech | --- **Document History** | Version | Date | Author | Changes | |---------|------|--------|---------| | 0.2-Draft | 01-06-2026 | O3DE Contributors | Initial draft | Copyright (c) Contributors to the Open 3D Engine Project.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully