• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orion-ecs / keen-eye / 20708234269

05 Jan 2026 07:27AM UTC coverage: 74.617% (-0.02%) from 74.632%
20708234269

push

github

tyevco
feat: Deprecate PrefabManager in favor of source-generated spawn methods

Mark all runtime prefab APIs with [Obsolete] attribute to guide users toward
compile-time .keprefab files with source-generated Scenes.SpawnXxx() methods.

Changes:
- Add [Obsolete] to PrefabManager, EntityPrefab, ComponentDefinition, IPrefabCapability
- Add [Obsolete] to World.Prefabs.cs methods (RegisterPrefab, SpawnFromPrefab, etc.)
- Create .keprefab sample files: BasicEnemy, Coin, FlyingEnemy, BossEnemy, FlyingBoss, Player
- Update sample Program.cs to demonstrate both approaches (generated + deprecated)
- Fix SceneGenerator to use world.Spawn() when entity name is empty/null
- Update docs/prefabs.md with deprecation notice and migration guide
- Add #pragma CS0618 suppressions for internal usage and tests

Closes #627

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

5690 of 7308 branches covered (77.86%)

Branch coverage included in aggregate %.

2 of 3 new or added lines in 1 file covered. (66.67%)

8 existing lines in 4 files now uncovered.

36262 of 48915 relevant lines covered (74.13%)

1.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.2
/src/KeenEyes.Assets/Systems/AssetResolutionSystem.cs
1
using System.Collections.Concurrent;
2

3
namespace KeenEyes.Assets;
4

5
/// <summary>
6
/// System that resolves <see cref="AssetRef{T}"/> components to loaded asset handles.
7
/// </summary>
8
/// <remarks>
9
/// <para>
10
/// This system runs in the EarlyUpdate phase and automatically loads assets referenced
11
/// by <see cref="AssetRef{T}"/> components. Once an asset is loaded, the component's
12
/// internal handle ID is set, and <see cref="AssetRef{T}.IsResolved"/> returns true.
13
/// </para>
14
/// <para>
15
/// Assets are loaded asynchronously with <see cref="LoadPriority.Normal"/> priority.
16
/// The system uses async loading to avoid frame hitches from loading large assets.
17
/// Pending load tasks are tracked and observed to prevent unobserved exceptions.
18
/// </para>
19
/// </remarks>
20
public sealed class AssetResolutionSystem : ISystem
21
{
22
    private IWorld? world;
23
    private AssetManager? assetManager;
24

25
    // Track pending loads with their tasks to observe exceptions
26
    private readonly ConcurrentDictionary<string, Task> pendingLoads = new();
1✔
27

28
    // Completed tasks to process on next update
29
    private readonly ConcurrentQueue<string> completedPaths = new();
1✔
30

31
    /// <inheritdoc />
32
    public bool Enabled { get; set; } = true;
1✔
33

34
    /// <inheritdoc />
35
    public void Initialize(IWorld world)
36
    {
37
        this.world = world;
1✔
38

39
        if (!world.TryGetExtension<AssetManager>(out assetManager))
1✔
40
        {
41
            // No asset manager - system will do nothing
42
            Enabled = false;
1✔
43
        }
44
    }
1✔
45

46
    /// <inheritdoc />
47
    public void Update(float deltaTime)
48
    {
49
        if (assetManager == null || world == null)
1✔
50
        {
51
            return;
1✔
52
        }
53

54
        // Process completed tasks first
55
        ProcessCompletedTasks();
1✔
56

57
        // Resolve texture assets
58
        ResolveAssets<TextureAsset>();
1✔
59

60
        // Resolve audio assets
61
        ResolveAssets<AudioClipAsset>();
1✔
62

63
        // Resolve mesh assets
64
        ResolveAssets<MeshAsset>();
1✔
65

66
        // Resolve raw assets
67
        ResolveAssets<RawAsset>();
1✔
68
    }
1✔
69

70
    private void ProcessCompletedTasks()
71
    {
72
        // Clean up completed tasks from the dictionary
73
        while (completedPaths.TryDequeue(out var path))
1✔
74
        {
75
            pendingLoads.TryRemove(path, out _);
1✔
76
        }
1✔
77
    }
1✔
78

79
    private void ResolveAssets<T>() where T : class, IDisposable
80
    {
81
        foreach (var entity in world!.Query<AssetRef<T>>())
1✔
82
        {
83
            ref var assetRef = ref world.Get<AssetRef<T>>(entity);
1✔
84

85
            // Skip if already resolved or no path set
86
            if (assetRef.IsResolved || !assetRef.HasPath)
1✔
87
            {
88
                continue;
89
            }
90

91
            // Check if already loaded
92
            if (assetManager!.IsLoaded(assetRef.Path))
1✔
93
            {
94
                // Asset is loaded, resolve the reference
95
                var handle = assetManager.Load<T>(assetRef.Path);
1✔
96
                assetRef.HandleId = handle.Id;
1✔
97
                continue;
1✔
98
            }
99

100
            // Check if load is pending
101
            if (pendingLoads.ContainsKey(assetRef.Path))
1✔
102
            {
103
                continue;
104
            }
105

106
            // Start async load and track the task
107
            var loadTask = LoadAssetAsync<T>(assetRef.Path);
1✔
108
            pendingLoads.TryAdd(assetRef.Path, loadTask);
1✔
109
        }
110
    }
1✔
111

112
    private async Task LoadAssetAsync<T>(string path) where T : class, IDisposable
113
    {
114
        try
115
        {
116
            await assetManager!.LoadAsync<T>(path, LoadPriority.Normal);
1✔
117
        }
1✔
118
        catch (Exception ex)
×
119
        {
120
            // Invoke error callback if configured
121
            assetManager!.OnLoadError?.Invoke(path, ex);
×
122
        }
×
123
        finally
124
        {
125
            // Queue for removal on next update
126
            completedPaths.Enqueue(path);
1✔
127
        }
128
    }
1✔
129

130
    /// <inheritdoc />
131
    public void Dispose()
132
    {
133
        // Wait for all pending loads to complete to ensure no unobserved exceptions
134
        var tasks = pendingLoads.Values.ToArray();
1✔
135
        if (tasks.Length > 0)
1✔
136
        {
UNCOV
137
            Task.WhenAll(tasks).ConfigureAwait(false).GetAwaiter().GetResult();
×
138
        }
139
        pendingLoads.Clear();
1✔
140
    }
1✔
141
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc