Godot C# – использование общего типа в качестве параметра сигнала

В ходе моего текущего проекта я обнаружил, что переписываю аналогичный шаблон во множестве разных классов для выдачи сигнала при каждом изменении переменной. В качестве примера целочисленной переменной этот код выглядит так:

[Signal] public delegate void ValueChangedEventHandler(int newValue);

private int _value;
public int Value
{
    get => _value;
    set
    {
        _value = value;
        EmitSignal(SignalName.ValueChanged, Value);
    }
}

К 20-й переменной, которую я создал с использованием этого формата, я начал уставать от беспорядка и повторений, поэтому решил, что могу создать вспомогательный класс, который обертывает эту функциональность. Я выполнил это с помощью:

using Godot;

public partial class NotifyOnChange<[MustBeVariant]T> : RefCounted
{
    [Signal] public delegate void ChangedEventHandler(Variant newValue);
    
    private T _value;
    public T Value
    {
        get => _value;
        set
        {
            _value = value;
            EmitSignal(SignalName.Changed, Variant.From(Value));
        }
    }

    public NotifyOnChange() { }
    public NotifyOnChange(T value)
    {
        Value = value;
    }
}

Это работает довольно хорошо и делает почти то, на что я надеялся, но у меня есть одна неприятная проблема, и я надеюсь, что есть обходной путь. Самой важной частью этой системы является сигнал, поскольку именно с ним будут взаимодействовать многие другие сценарии. Однако единственный способ объявить этот сигнал без ошибок — придать ему тип Variant. Это означает, что в каждом скрипте, подписывающемся на этот сигнал, сигнатура метода, ожидаемая для методов, подписанных на этот сигнал, будет выглядеть так:

private void OnValueChanged(Variant newValue)
{ 
    // Logic here...
}

Моя проблема в том, что каждый из этих методов будет иметь параметр типа Variant, требующий каждый раз приведения к нужному типу. Это добавляет накладные расходы, поскольку мне нужно будет запомнить, какого типа должно быть значение, и я обнаружил, что это добавляет больше хлопот, чем пользы, которую я получаю от использования этой системы.

Итак, мой вопрос:

Есть ли способ объявить сигнал, используя общий тип, например:

[Signal] public delegate void ChangedEventHandler(T newValue);

Попытка объявить сигнал таким образом в классе, который я показал выше, приводит к тому, что Godot не генерирует серверную часть сигнала и в целом, похоже, не работает. Я нахожу такое поведение странным, поскольку я пометил общий параметр T атрибутом [MustBeVariant] в объявлении класса, поэтому я ожидаю, что не должно возникнуть никаких проблем с использованием T вместо Variant для типа параметра сигнала.

Есть ли что-то, чего мне здесь не хватает, или это просто то, что пока невозможно в Годо?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
148
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Для всех, кто сталкивался с этим, я до сих пор не нашел способа сделать это с помощью сигналов Godot, но мне удалось заставить эту функциональность работать с помощью событий C#.

Это было достигнуто путем простого объявления делегата с параметром типа T, а затем создания события с использованием этого делегата.

Чтобы максимизировать совместимость и эффективность, я создал систему, которая позволяет использовать как событие универсального типа, так и сигнал вариантного типа. В целом это выглядит так:

using Godot;

public partial class NotifyOnChange : RefCounted
{
    [Signal] public delegate void VariantChangedEventHandler(Variant newValue);
    
    private Variant _variantValue;
    public Variant VariantValue
    {
        get => _variantValue;
        set
        {
            _variantValue = value;
            EmitSignal(SignalName.VariantChanged, VariantValue);
        }
    }
    
    public NotifyOnChange() { }
    public NotifyOnChange(Variant value)
    {
        VariantValue = value;
    }
}

public partial class NotifyOnChange<[MustBeVariant]T> : NotifyOnChange
{
    public delegate void ChangedEventHandler(T newValue);
    public event ChangedEventHandler Changed;
    
    public T Value
    {
        get => VariantValue.As<T>();
        set => VariantValue = Variant.From(value);
    }

    public NotifyOnChange()
    {
        VariantChanged += newValue => Changed?.Invoke(newValue.As<T>());
    }
    
    public NotifyOnChange(T value)
    {
        Value = value;
    }
}

Хотя было бы здорово получить от Godot официальную поддержку сигналов общего типа, этот обходной путь работает отлично и обеспечивает большую гибкость!

Другие вопросы по теме