在 Update() 中创建新类会导致 StackOverflow 异常 Unity

发布于 2025-01-16 19:48:05 字数 1791 浏览 0 评论 0原文

我有一个 IPawnInput 接口,它有一个 OnInput(object sender) 方法用于输入。之前,我只是通过“主”类 - Player 为其他组件(PlayerMovement、PlayerCamera)调用此方法。很快,我在将所有必要的组件连接到主要组件时遇到了问题。我决定有一个输入管理器,在所有必需的组件上调用此方法。因为该管理器本身应该从上面获取输入命令,所以我创建了一个 PlayerInputManagerSender 类,其中包含有关输入请求的原始发送者的信息。直接发送原始发送者不是一个选择,因为某些组件需要知道它们是通过管理器使用的。由于某种原因,创建此类会导致 Stackoverflow 错误。我知道每帧调用方法并创建一个类并不是最好的解决方案,但我没有其他解决方案,因为某些组件中的输入必须每帧读取。

using System.Collections.Generic;
using UnityEngine;

namespace LOK1game
{
    public class PlayerInputManager : MonoBehaviour, IPawnInput
    {
        [HideInInspector] public List<IPawnInput> PawnInputs = new List<IPawnInput>();

        [SerializeField] private List<MonoBehaviour> _actors;

        private void Awake()
        {
            foreach (var actor in _actors)
            {
                if(actor is PlayerInputManager)
                {
                    throw new System.Exception("Input manager can not calls itself!");
                }

                PawnInputs.Add(actor.GetComponent<IPawnInput>());
            }
        }

        private void Update()
        {
            //Test temp solution
            OnInput(this);
        }

        public void OnInput(object sender)
        {
            var managerSender = new PlayerInputManagerSender(sender, this);

            foreach (var pawn in PawnInputs)
            {
                pawn.OnInput(managerSender);
            }
        }
    }

    public struct PlayerInputManagerSender
    {
        public object OriginSender;
        public object ActualSender;

        public PlayerInputManagerSender(object originSender, object actualSender)
        {
            OriginSender = originSender;
            ActualSender = actualSender;
        }
    }
}

我刚刚尝试将类更改为结构

I have an IPawnInput interface that has an OnInput(object sender) method for input. Before, I just called this method for the other components (PlayerMovement, PlayerCamera) through the "main" class - Player. Soon I had problems linking all the necessary components to the main one. I decided to have an input manager that calls this method on all the required components. Because this manager itself should get the input command from above, I created a class PlayerInputManagerSender that has information about the original sender of the input request. Sending the original sender directly is not an option, because some components need to know that they were used through the manager. For some reason creating this class causes a Stackoverflow error. I understand that calling method and creating a class every frame isn't the best solution, but I have no other solution, because input in some components must be read every frame.

using System.Collections.Generic;
using UnityEngine;

namespace LOK1game
{
    public class PlayerInputManager : MonoBehaviour, IPawnInput
    {
        [HideInInspector] public List<IPawnInput> PawnInputs = new List<IPawnInput>();

        [SerializeField] private List<MonoBehaviour> _actors;

        private void Awake()
        {
            foreach (var actor in _actors)
            {
                if(actor is PlayerInputManager)
                {
                    throw new System.Exception("Input manager can not calls itself!");
                }

                PawnInputs.Add(actor.GetComponent<IPawnInput>());
            }
        }

        private void Update()
        {
            //Test temp solution
            OnInput(this);
        }

        public void OnInput(object sender)
        {
            var managerSender = new PlayerInputManagerSender(sender, this);

            foreach (var pawn in PawnInputs)
            {
                pawn.OnInput(managerSender);
            }
        }
    }

    public struct PlayerInputManagerSender
    {
        public object OriginSender;
        public object ActualSender;

        public PlayerInputManagerSender(object originSender, object actualSender)
        {
            OriginSender = originSender;
            ActualSender = actualSender;
        }
    }
}

I've just tried to change class to struct

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

百合的盛世恋 2025-01-23 19:48:05

我的猜测是你有循环引用。 PlayerInputManager 是一个IPawnInput,并拥有一个IPawnInput 列表。因此,其他一些 IPawnInput 对象可能包含对所属对象的引用。本质上形成一个图表。因此,当您调用 OnInput 时,您最终会陷入无限递归,并最终耗尽堆栈。

要检查这一点,您可以迭代图表并检查是否有任何对象出现两次:

public static bool HasCycle<T>(T self, Func<T, IEnumerable<T>> selector, IEqualityComparer<T> comparer = default)
{
    var stack = new Stack<T>();
    stack.Push(self);
    comparer ??= EqualityComparer<T>.Default;
    var visited = new HashSet<T>(comparer);
    while (stack.Count > 0)
    {
        var current = stack.Pop();
        if (!visited.Add(current))
        {
            return true;
        }
        foreach (var child in selector(current))
        {
            stack.Push(child);
        }
    }
    return false;
}

调用方式

var result = HasCycle(this, p => p.PawnInputs);

您可以修改上面的迭代代码以给出出现两次的对象,或者在每次添加 pawn 时调用它以查找循环的位置创建并解决问题。

您还可以使用类似的方法来忽略图中的任何重复项,以防您实际上打算有循环,但希望处理其中的每个项一次。

My guess is that you have cyclic references. PlayerInputManager is a IPawnInput, and owns a list of IPawnInput. So there is the possibility that some other IPawnInput object contains a reference to the owning object. Essentially forming a graph. So when you call OnInput you end up with infinite recursion, and eventually run out of stack.

To check for this you may iterate over the graph and check if any object occurs twice:

public static bool HasCycle<T>(T self, Func<T, IEnumerable<T>> selector, IEqualityComparer<T> comparer = default)
{
    var stack = new Stack<T>();
    stack.Push(self);
    comparer ??= EqualityComparer<T>.Default;
    var visited = new HashSet<T>(comparer);
    while (stack.Count > 0)
    {
        var current = stack.Pop();
        if (!visited.Add(current))
        {
            return true;
        }
        foreach (var child in selector(current))
        {
            stack.Push(child);
        }
    }
    return false;
}

Called like

var result = HasCycle(this, p => p.PawnInputs);

You could modify the iteration code above to give you what object occurs twice, or call it every time you add a pawn to find where the cycle is created, and fix the issue.

You can also use a similar approach to just ignore any duplicated items in the graph in case you actually intend to have cycles, but want to process each item in it once.

沫离伤花 2025-01-23 19:48:05

StackOverflow异常通常是由无限递归引起的。您的一个函数正在调用另一个函数,该函数以某种方式回调最初调用的函数。在 OnInput() 和 Update() 函数中使用断点/写入行,以确保它们被调用的次数符合您的预期。

StackOverflow exceptions are often causes by infinite recursion. One of your functions is calling another function that calls back to the originally called function somehow. Use breakpoints/writelines in your OnInput() and Update() functions to make sure they're being called the appropriate number of times for what you expect.

五里雾 2025-01-23 19:48:05

您的类 PlayerInputManager 正在实现 IPawnInput 本身,因此当您这样做时

foreach (var actor in _actors)
{
    if(actor is PlayerInputManager)
    {
        throw new System.Exception("Input manager can not calls itself!");
    }

    PawnInputs.Add(actor.GetComponent<IPawnInput>());
}

actor 本身很可能不是 PlayerInputManager组件,但另一个附加到同一 GameObject 的组件。

=>不会阻止 PlayerInputManager 将其放入 PawnInputs

您应该像 after GetComponent 那样进行检查

foreach (var actor in _actors)
{
    if(actor.TryGetComponent<IPawnInput>(out var pawnInput))
    { 
        if(pawnInput is PlayerInputManager)
        {
            // personally I would just ignore it and not throw an exception
            // that seems a bit overkill ;)
            continue;
        }

        PawnInputs.Add(pawnInput);
    }
}

Your class PlayerInputManager is implementing IPawnInput itself so when you do

foreach (var actor in _actors)
{
    if(actor is PlayerInputManager)
    {
        throw new System.Exception("Input manager can not calls itself!");
    }

    PawnInputs.Add(actor.GetComponent<IPawnInput>());
}

it is very possible that actor itself is not the PlayerInputManager component but another one that is attached to the same GameObject.

=> It is not prevented that a PlayerInputManager makes it into the PawnInputs!

You should do the check rather after GetComponent<IPawnInput> like

foreach (var actor in _actors)
{
    if(actor.TryGetComponent<IPawnInput>(out var pawnInput))
    { 
        if(pawnInput is PlayerInputManager)
        {
            // personally I would just ignore it and not throw an exception
            // that seems a bit overkill ;)
            continue;
        }

        PawnInputs.Add(pawnInput);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文