scellecs / morpeh

🎲 ECS Framework for Unity Game Engine and .Net Platform
MIT License
532 stars 42 forks source link

Filter is null when trying to World.(Any)Update(deltaTime) #193

Closed RomanNiki closed 1 year ago

RomanNiki commented 1 year ago

Filter is null when trying to World.(Any)Update(deltaTime). After install an update from Commits on Sep 8, 2023. Filters are initialized in OnAwake.

RomanNiki commented 1 year ago

After the rollback, everything was fixed.

olegmrzv commented 1 year ago

Thanks for the contribution. Can you attach a minimal reproduction case? Or maybe attach exception message with code line?

In my example everything works, I can’t find what’s broken.

using Scellecs.Morpeh;
using UnityEngine;

public class Bootstrap : MonoBehaviour {
    private void Start() {
        World.Default.UpdateByUnity = false;
        var sg = World.Default.CreateSystemsGroup();
        sg.AddSystem(new FooSystem());
        World.Default.AddSystemsGroup(0, sg);
    }

    private void Update() {
        World.Default.Update(Time.deltaTime);
    }

    private void FixedUpdate() {
        World.Default.FixedUpdate(Time.fixedDeltaTime);
    }

    private void LateUpdate() {
        World.Default.LateUpdate(Time.deltaTime);
    }
}

public struct TestComponent0 : IComponent {
    public int test;
}

public class FooSystem : ISystem {
    public World World { get; set; }

    private Filter filter;
    private Stash<TestComponent0> stash;

    public void OnAwake() {
        this.filter = this.World.Filter.With<TestComponent0>().Build();
        this.stash = this.World.GetStash<TestComponent0>();

        for (int i = 0, length = 1000; i < length; i++) {
            var e = this.World.CreateEntity();
            e.AddComponent<TestComponent0>();
        }
    }
    public void OnUpdate(float deltaTime) {
        foreach (var entity in this.filter) {
            this.stash.Get(entity).test++;
        }

    }

    public void Dispose() {
    }
}
RomanNiki commented 1 year ago

stack trace NullReferenceException: Object reference not set to an instance of an object Scellecs.Morpeh.Filter+EntityEnumerator.Dispose () (at ./Library/PackageCache/com.scellecs.morpeh@b4983589c5/Core/Filter.cs:182) Core.Characteristics.EnergyLimits.Systems.ChargeSystem.OnUpdate (System.Single deltaTime) (at Assets/Scripts/Core/Characteristics/EnergyLimits/Systems/ChargeSystem.cs:32) Scellecs.Morpeh.SystemsGroupExtensions.TryCatchUpdate (Scellecs.Morpeh.ISystem system, Scellecs.Morpeh.SystemsGroup systemsGroup, System.Single deltaTime) (at ./Library/PackageCache/com.scellecs.morpeh@b4983589c5/Core/SystemsGroupExtensions.cs:187) UnityEngine.Debug:LogException(Exception) Scellecs.Morpeh.Logging.MorpehUnityLogger:Scellecs.Morpeh.Logging.IMorpehLogger.LogException(Exception) (at ./Library/PackageCache/com.scellecs.morpeh@b4983589c5/Core/Logging/MorpehUnityLogger.cs:11) Scellecs.Morpeh.MLogger:LogException(Exception) (at ./Library/PackageCache/com.scellecs.morpeh@b4983589c5/Core/MLogger.cs:39) Scellecs.Morpeh.SystemsGroupExtensions:SystemThrowException(SystemsGroup, ISystem, Exception) (at ./Library/PackageCache/com.scellecs.morpeh@b4983589c5/Core/SystemsGroupExtensions.cs:175) Scellecs.Morpeh.SystemsGroupExtensions:TryCatchUpdate(ISystem, SystemsGroup, Single) (at ./Library/PackageCache/com.scellecs.morpeh@b4983589c5/Core/SystemsGroupExtensions.cs:193) Scellecs.Morpeh.SystemsGroupExtensions:Update(SystemsGroup, Single) (at ./Library/PackageCache/com.scellecs.morpeh@b4983589c5/Core/SystemsGroupExtensions.cs:74) Scellecs.Morpeh.WorldExtensions:Update(World, Single) (at ./Library/PackageCache/com.scellecs.morpeh@b4983589c5/Core/WorldExtensions.cs:218) Scellecs.Morpeh.WorldExtensions:GlobalUpdate(Single) (at ./Library/PackageCache/com.scellecs.morpeh@b4983589c5/Core/WorldExtensions.cs:185) Scellecs.Morpeh.UnityRuntimeHelper:Update() (at ./Library/PackageCache/com.scellecs.morpeh@b4983589c5/Unity/UnityRuntimeHelper.cs:70) the System:

using Core.Characteristics.EnergyLimits.Components;
using Core.Extensions;
using Scellecs.Morpeh;
using UnityEngine;

namespace Core.Characteristics.EnergyLimits.Systems
{
#if ENABLE_IL2CPP
    using Unity.IL2CPP.CompilerServices;

    [Il2CppSetOption(Option.NullChecks, false)]
    [Il2CppSetOption(Option.ArrayBoundsChecks, false)]
#endif

    public sealed class ChargeSystem : ISystem
    {
        private Filter _filter;
        private Stash<Energy> _energyPool;
        private Stash<ChargeRequest> _chargeRequestPool;

        public World World { get; set; }

        public void OnAwake()
        {
            _filter = World.Filter.With<ChargeRequest>().Build();
            _energyPool = World.GetStash<Energy>();
            _chargeRequestPool = World.GetStash<ChargeRequest>();
        }

        public void OnUpdate(float deltaTime)
        {
            foreach (var requestEntity in _filter)
            {
                ref var chargeRequest = ref _chargeRequestPool.Get(requestEntity);
                ref var chargeRequestEntity = ref chargeRequest.Entity;
                ref var energy = ref _energyPool.Get(chargeRequestEntity);
                var chargeAmount = chargeRequest.Value;
                energy.Value = Mathf.Min(energy.MaxValue, energy.Value + chargeAmount);

                World.SendMessage(new EnergyChangedEvent { Entity = chargeRequestEntity });

                World.RemoveEntity(requestEntity);
            }
        }

        public void Dispose()
        {
        }
    }
}

filter is not initialized in all systems. In debug mode awake is called and is called before update (as it should be) but for some reason the filter has null reference already in update

RomanNiki commented 1 year ago

My Bootstrap


using System;
using System.Linq;
using Core.Extensions;
using Core.Extensions.Views;
using Core.Movement;
using Cysharp.Threading.Tasks;
using Engine.Factories.SystemsFactories;
using Scellecs.Morpeh;
using TriInspector;
using UnityEngine;
using Zenject;
using EntityProvider = Engine.Providers.EntityProvider;

public sealed class GameplayStartup : BaseInstaller
{
    [ReadOnly]
    [SerializeReference] private BaseMorpehFeature[] _activeFeatures = Array.Empty<BaseMorpehFeature>();
    private IFeaturesFactory _featuresesFactory;
    private IMoveLoopService _moveLoopService;
    private SystemFactoryArgs _systemFactoryArgs;

    [Inject]
    public void Constructor(World world, IFeaturesFactory featuresesFactory, SystemFactoryArgs systemFactoryArgs)
    {
        _systemFactoryArgs = systemFactoryArgs;
        World = world;
        World.UpdateByUnity = true;
        _featuresesFactory = featuresesFactory;

        CreateFeatures();
    }

    protected override async void OnEnable()
    {
        await EnableFeatures();
    }

    protected override void OnDisable()
    {
        DisableFeatures();
    }

    private async UniTask EnableFeatures()
    {
        var order = 0;
        for (var i = 0; i < _activeFeatures.Length; i++, order++)
            await World.AddFeatureAsync(order, _activeFeatures[i]);
    }

    private void DisableFeatures()
    {
        for (var i = 0; i < _activeFeatures.Length; i++)
            World.RemoveFeature(_activeFeatures[i]);
    }

    private void Start()
    {
        foreach (var entity in FindObjectsOfType<EntityProvider>())
        {
            entity.Init(World);
        }
    }

    private void OnDestroy()
    {
        World?.Dispose();
    }

    private void CreateFeatures()
    {
        _activeFeatures = _featuresesFactory.CreateFeatures(_systemFactoryArgs).ToArray();
    }
}
RomanNiki commented 1 year ago

The problem is in EntityEnumerator, because when calling GetEnumerator in a filter with the number of archtypes = 0, default is returned and there the world is null. And when foreach tried to call Dispose(), NullReferenceException was thrown.

olegmrzv commented 1 year ago

Thanks for your investigation. Fix is already in stage 2023.