using SystemAPI inside custom types

Hello Everyone,

I’m currently working on streamlining my Ability System framework by consolidating repetitive code commonly used across various systems. This includes tasks like declaring, initializing, and updating lookups, as well as accessing certain singletons that use doesn’t even have to know about.

However, I’ve encountered an issue: it appears that using SystemAPI methods, such as GetSingleton, is restricted outside of Systems.
Error: EA0004: You may not use the SystemAPI member GetSingleton outside of a system. SystemAPI members rely on setup from the containing system.

public struct AbilitySystemContext
    {
        [ReadOnly] public GameplayActionsFunctionsPointersSingleton GameplayActionsFunctionsPointersSingleton;
        [ReadOnly] public AbilityResourcesSingleton AbilityResourcesSingleton;
        [ReadOnly] public BufferLookup<ActorGameplayAttributes> ActorGameplayAttributesLookup;
        [ReadOnly] public ComponentLookup<ActorGameplayTags> ActorGameplayTagsLookup;
        [ReadOnly] public ComponentLookup<AbilityInstanceDefinition> AbilityInstanceDefinitionLookup;
        [ReadOnly] public EntityStorageInfoLookup EntityStorageInfoLookup;

        public void OnCreate(ref SystemState state)
        {
            //Declare the Requirements for the Implementing System
            state.RequireForUpdate<GameplayActionsFunctionsPointersSingleton>();
            state.RequireForUpdate<AbilityResourcesSingleton>();

            //Initialize the Component Lookups
            ActorGameplayAttributesLookup = state.GetBufferLookup<ActorGameplayAttributes>(true);
            ActorGameplayTagsLookup = state.GetComponentLookup<ActorGameplayTags>(true);
            AbilityInstanceDefinitionLookup = state.GetComponentLookup<AbilityInstanceDefinition>(true);
            EntityStorageInfoLookup = state.GetEntityStorageInfoLookup();
        }

        public void OnUpdate(ref SystemState state)
        {
            //Update Components Lookups
            ActorGameplayAttributesLookup.Update(ref state);
            ActorGameplayTagsLookup.Update(ref state);
            AbilityInstanceDefinitionLookup.Update(ref state);
            EntityStorageInfoLookup = state.GetEntityStorageInfoLookup();
            GameplayActionsFunctionsPointersSingleton = SystemAPI.GetSingleton<GameplayActionsFunctionsPointersSingleton>();
            AbilityResourcesSingleton = SystemAPI.GetSingleton<AbilityResourcesSingleton>();
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public BlobAssetReference<GameplayAbilityDefinition> GetAbilityDefinition(Entity abilityInstance) =>
            this.AbilityInstanceDefinitionLookup[abilityInstance].AbilityDefinition;
    }

Does anyone know of a hidden API or a workaround that could help me bypass this limitation?
Any suggestions would be greatly appreciated.

1 Like

It’s just EntityQuery.GetSingleton

So just create a query in OnCreate and call GetSingleton on it - though this becomes problematic when you are passing the struct to a job so for my util I usually have a separate container struct and then it creates the data struct to pass to the job.

2 Likes

Thanks @tertle

as a workaround, I stored all the singleton components’ EntityQueries in one struct and used IntPtr to access them. This way I don’t have to make a new model for Jobs.
I hope Unity will provide an API for this soon.

 public struct AbilitySystemContext : IDisposable
    {
        [ReadOnly] public SingletonComponent1 SingletonComponent1;
        [ReadOnly] public SingletonComponent2 SingletonComponent2; 

        [NativeDisableUnsafePtrRestriction, ReadOnly]
        private IntPtr _singletonQueriesPtr;

        private struct Singletons
        {
            public EntityQuery EntityQueryComponent1;
            public EntityQuery EntityQueryComponent2;
        }

        public unsafe void OnCreate(ref SystemState state)
        {
            //Declare the Requirements for the Implementing System
            state.RequireForUpdate<SingletonComponent1>();
            state.RequireForUpdate<SingletonComponent2>();

     
            var entityQuerySingletons = new Singletons()
            {
                EntityQueryComponent1 = state.GetEntityQuery(ComponentType.ReadOnly<SingletonComponent1>()),
                EntityQueryComponent2 = state.GetEntityQuery(ComponentType.ReadOnly<SingletonComponent2>()),
            };

            var singletonsPtr = UnsafeUtility.Malloc(UnsafeUtility.SizeOf<Singletons>(), UnsafeUtility.AlignOf<Singletons>(), Allocator.Persistent);
            UnsafeUtility.CopyStructureToPtr(ref entityQuerySingletons, singletonsPtr);
            _singletonQueriesPtr = new IntPtr(singletonsPtr);
        }

        public unsafe void OnUpdate(ref SystemState state)
        {
            
            ref var singletons = ref UnsafeUtility.AsRef<Singletons>(_singletonQueriesPtr.ToPointer());

            SingletonComponent1 = singletons.EntityQueryComponent1.GetSingleton<SingletonComponent1>();
            SingletonComponent2 = singletons.EntityQueryComponent2.GetSingleton<SingletonComponent2>();
        }
 
        public unsafe void Dispose()
        {
            UnsafeUtility.Free(_singletonQueriesPtr.ToPointer(), Allocator.Persistent);
        }
    }
2 Likes