Почему не работает событие после перекомпиляции?

Программирование на Юнити.

Почему не работает событие после перекомпиляции?

Сообщение iprogrammer 29 июн 2018, 13:31

У меня есть кастомное окно EditorWindow, вспомогательная панель (ScriptableObject) и куча стандартных классов, которые рисуют себя на панели. У меня много разных событий в этих классах. Когда я запускаю окно - всё работает, то если изменяю что-то в скриптах и сохраняю, то Unity производит перекомпиляцию (Unity сериализцет и десериализцет данные в этом время) и после этого события перестают работать. Пример:


MainWindow:
Синтаксис:
Используется csharp

    public class MainWindow : EditorWindow  {
   
        public static MainWindow Instance;
        public AuxPanel auxPanel;
       
        [MenuItem("Examples/MainWindow")]
        static void InitAndRun() {
            if (Instance == null) {            
                Init();
            } else {            
                GetWindow<MainWindow>();
                Instance.Focus();
            }                
        }    
       
        private void OnEnable() {
            if (Instance == null) Instance = GetWindow<MainWindow>();        
        }
   
        private static void Init(String initForm = null) {            
            MainWindow mainWindow = GetWindow<MainWindow>();
            Instance = mainWindow;                                    
            mainWindow.InitializeInstance(initForm);
            mainWindow.Show();
        }
       
        private void InitializeInstance(String initForm) {    
            auxPanel = CreateInstance<AuxPanel>().Initialize();  
        }
               
        private void OnGUI() {
            auxPanel.Draw();
        }
    }
 


AuxPanel:
Синтаксис:
Используется csharp
    public class AuxPanel : ScriptableObject {
   
        public Rect DrawRect = new Rect(50, 20, 140, 140);
   
        [SerializeField]
        public ElementWithSubscribing myElement { get; set; }
        public UnityAction<Vector2> PositionChange = delegate { };
   
        public AuxPanel Initialize() {
            myElement = new ElementWithSubscribing(Vector2.zero, this);
            return this;
        }
   
        public void Draw() {
            GUI.Label(DrawRect, "", "GroupBox");
            myElement.Draw();
            // fire event
            PositionChange(DrawRect.position);
        }
    }
 

ElementSubscriber:
Синтаксис:
Используется csharp
    [Serializable]
    public class ElementWithSubscribing {
   
        public Rect DrawRect = new Rect(0, 0, 100, 50);
       
        public ElementWithSubscribing() { }
        public ElementWithSubscribing(Vector2 position) { }
        public ElementWithSubscribing(Vector2 position, AuxPanel auxPanel) {
            auxPanel.PositionChange += UpdatePosition;
        }
   
        protected void UpdatePosition(Vector2 designerPosition) {
            Debug.Log("event works!");
            DrawRect.position = designerPosition;
        }
   
        public void Draw() {
            GUI.Button(DrawRect, "MyElement");
        }
    }
 

После сохранения кода - ElementWithSubscribing теряет подписку на событие auxPanel.PositionChange

Как сохранить событие? Или как его восстановить после компиляции? У меня много разных событий и много других классов, поэтому вариант переприкрепить временно события к какому-то объекту, который будет сохранять события - как-то не ахти

———————————————————————————————————————————

P.S. А еще данный код после компиляции иногда выдает мне два окна. То есть одно было → компиляция → еще одно окно появилось:

Изображение

Как это тоже поправить?
iprogrammer
UNец
 
Сообщения: 41
Зарегистрирован: 25 июн 2016, 07:10

Re: Почему не работает событие после перекомпиляции?

Сообщение ilkalawson 29 июн 2018, 17:12

То есть одно было → компиляция → еще одно окно появилось:

x_x ну конечно у вас же статическая Instance обнуляется после компиляции.

остальной код я пока вообще не могу с точки зрения логики понять, предвкушаю что у вас как всегда хромает организация.
ilkalawson
UNIверсал
 
Сообщения: 401
Зарегистрирован: 19 янв 2015, 20:38
Skype: lawsonunity

Re: Почему не работает событие после перекомпиляции?

Сообщение Cr0c 29 июн 2018, 20:56

Ага, рекомпиляция ломает все статики ))
Аватара пользователя
Cr0c
Адепт
 
Сообщения: 2808
Зарегистрирован: 19 июн 2015, 13:50

Re: Почему не работает событие после перекомпиляции?

Сообщение iprogrammer 29 июн 2018, 22:22

ilkalawson писал(а): x_x ну конечно у вас же статическая Instance обнуляется после компиляции.

Так, а разве вот это
Синтаксис:
Используется csharp
private void OnEnable() {
    if (Instance == null) Instance = GetWindow<MainWindow>();        
}
 

не должно чинить проблему? Ведь GetWindow<MainWindow>() берет экземпляр уже существующего окна и возвращает его. Нет? И почему это проблема проявляется изредка, как бог на душу положит, а не всегда?

ilkalawson писал(а): остальной код я пока вообще не могу с точки зрения логики понять, предвкушаю что у вас как всегда хромает организация.

А что тут понимать? Есть главное окно с панелями, на панелях рисуются свои какие-либо элементы. Всё. Что тут еще понимать?)))
У главного окна в OnGUI запускаются методы Draw каждой панели, а в каждой панели внутри запускаются методы Draw каждого элемента, что ей принадлежит для отрисовки. Хромой организации я тут не наблюдаю. Что в ней хромого? (агалогичную штуку, только на примере Update рассматривают тут: https://blogs.unity3d.com/ru/2015/12/23 ... ate-calls/) :-B
Да и причем тут организация, если вот самый простой код, представленный в 1-ом сообщении не работает?
iprogrammer
UNец
 
Сообщения: 41
Зарегистрирован: 25 июн 2016, 07:10

Re: Почему не работает событие после перекомпиляции?

Сообщение iprogrammer 02 июл 2018, 18:26

Решился я сделать всё же глобальный EventManager, как это у Microsoft в winforms (не знаю как в других его приложениях) сделано (у них там Events, который является списком и хранит ключ - объект, значение - делегат). Хотя знать ответ, почему всё же не работают события так и что надо бы сделать - было бы круто... но... видимо никто не знает)))

В итоге получилось так...сам менеджер:
Синтаксис:
Используется csharp
public class EventManager  {

        public static EventManager Instance {
                get {
                        if (instance == null) {
                                instance = new EventManager(); // instance = new EventManager3();

                                if (instance == null) {  
                                        Debug.LogError("There needs to be one active EventManger script on a GameObject in your scene.");
                                } else {
                                        instance.Init();
                                }
                        }

                        return instance;
                }
        }

        public delegate void EventDelegate<T>(T e) where T : EventArgs;

        private static EventManager instance;
        private delegate void EventDelegate(EventArgs e);
        private Dictionary<object, EventDelegate> delegates;
        private Dictionary<Delegate, EventDelegate> delegateLookup;


        void Init() {
                delegates = delegates ?? new Dictionary<object, EventDelegate>();
                delegateLookup = delegateLookup ??  new Dictionary<Delegate, EventDelegate>();
        }


        public static void AddListener<T>(object eventName, EventDelegate<T> del) where T : EventArgs {
                if (Instance.delegateLookup.ContainsKey(del))
                        return;

                EventDelegate internalDelegate = e => del((T)e);
                Instance.delegateLookup[del] = internalDelegate;

                EventDelegate tempDel;
                if (Instance.delegates.TryGetValue(eventName, out tempDel))
                        Instance.delegates[eventName] = tempDel += internalDelegate;
                else
                        Instance.delegates[eventName] = internalDelegate;
        }


        public static void RemoveListener<T>(object eventName, EventDelegate<T> del) where T : EventArgs {
                EventDelegate internalDelegate;
                if (Instance.delegateLookup.TryGetValue(del, out internalDelegate)) {
                        EventDelegate tempDel;
                        if (Instance.delegates.TryGetValue(eventName, out tempDel)) {
                                tempDel -= internalDelegate;
                                if (tempDel == null)
                                        Instance.delegates.Remove(eventName);
                                else
                                        Instance.delegates[eventName] = tempDel;
                        }

                        Instance.delegateLookup.Remove(del);
                }
        }


        public static int DelegateLookupCount => Instance.delegateLookup.Count;
        public static void Raise(object eventName, EventArgs e) {
                EventDelegate del;
                if (Instance.delegates.TryGetValue(eventName, out del))
                        del.Invoke(e);
        }

        public static void ClearAllEvents() {
                Instance.delegateLookup.Clear();
                Instance.delegates.Clear();
        }
}
 


список хранения событий:
Синтаксис:
Используется csharp
public class EventsListApp {
        public static readonly object EventTestYoYo  = new object();        
        // ...
        //    public const string EVENT_TEST = "EVENT_TEST";
}
 


ну и какой-нибудь кастомный класс, который отнаследован от EventArgs
Синтаксис:
Используется csharp
public class TestEventArgs : EventArgs {
        public int id;
        public string test;
}
 


В случае вызова без аргументов естественно будет EventArgs.Empty

Как-то так
iprogrammer
UNец
 
Сообщения: 41
Зарегистрирован: 25 июн 2016, 07:10

Re: Почему не работает событие после перекомпиляции?

Сообщение Tolking 02 июл 2018, 20:58

Тебе написали во 2 сообщении причину. И по этому, скорее всего, не будет работать и этот глобальный...
Ковчег построил любитель, профессионалы построили Титаник.
Аватара пользователя
Tolking
Адепт
 
Сообщения: 2119
Зарегистрирован: 08 июн 2009, 18:22
Откуда: Тула

Re: Почему не работает событие после перекомпиляции?

Сообщение iprogrammer 02 июл 2018, 22:40

Tolking писал(а):Тебе написали во 2 сообщении причину. И по этому, скорее всего, не будет работать и этот глобальный...


Там написана причина появления второго окна. И то на мои последующие вопросы об этом - не ответили. А баг плавающий. По поводу событий там ничего не написано. Совсем.
И ведь весь остальной код в программе работает. Абсолютно весь. И его много. А события отключаются. Не находите, что тогда бы вся программа падала бы и не работала в таком случае?

Глобальный менеджер вполне себе работает, потому что я его сделал и проверил, а не просто на шару по пьяни здесь какую-то финтифлюху плюхнул.
iprogrammer
UNец
 
Сообщения: 41
Зарегистрирован: 25 июн 2016, 07:10

Re: Почему не работает событие после перекомпиляции?

Сообщение ilkalawson 03 июл 2018, 13:58

Когда я запускаю окно - всё работает, то если изменяю что-то в скриптах и сохраняю, то Unity производит перекомпиляцию (Unity сериализцет и десериализцет данные в этом время) и после этого события перестают работать

Главный вопрос был этот, судя по первому сообщению? Unity все несерериализуемые данные сбрасывает после компиляции, вот почему у вас не работает. Можете придумать кучу других способов от Microsoft или откуда еще, но эту проблему вам не избежать.

Но если все работает, я за вас конечно рад, тогда получается что вопрос был вообще не в этом.
ilkalawson
UNIверсал
 
Сообщения: 401
Зарегистрирован: 19 янв 2015, 20:38
Skype: lawsonunity

Re: Почему не работает событие после перекомпиляции?

Сообщение iprogrammer 03 июл 2018, 19:23

ilkalawson писал(а):Главный вопрос был этот, судя по первому сообщению? Unity все несерериализуемые данные сбрасывает после компиляции, вот почему у вас не работает.

Не совсем так. Никто ничего не сбрасывает, все сохраняется в нужных местах))) Тем более, присмотритесь, у класса стоит атрибут [Serializable], что дает понять Юнити, что этот класс надо тоже сериализовать (если он как поле есть в другом классе). На самом деле при десериализации (хотя и при сериализации тоже) идет попытка найти дефолтный конструктор класса и вызывается именно он. И вот поэтому в данном случае событие перестает работать, потому что в дефолтном конструкторе подписки нет.

Можно наделать статических эвентов, т.е. на примере моего кода можно сделать public static UnityAction<Vector2> PositionChange = delegate { }; и в нужных местах подписаться AuxPanel.PositionChange += UpdatePosition;......но изначально планировалось, что тот же потомок вообще ничего не должен знать о родителе и не хранить его у себя внутри. Поэтому у auxPanel в конструкторе ElementWithSubscribing лишь на мгновение для подписки на событие. Да и вообще статические события, на сколько я помню - это не очень хорошо.

ilkalawson писал(а):Но если все работает, я за вас конечно рад, тогда получается что вопрос был вообще не в этом.

Об этом, об этом...
------------------------------------------------------------------------
А что скажите на счет второго экземпляра окна? Я там выше задавал эти вопросы. Почему эта проблема проявляется изредка, как бог на душу положит, а не каждый раз после компиляции?
И я допускаю, что это неверно написано, поэтому спрошу: как правильно сделать, чтобы нажав в меню - появлялось окно и инициализировались данные, а если есть окно и нажимаешь в меню или после компиляции - просто фокус ставился на него? Честно говоря раньше работало, но что-то сделал - и перестало :)) :))
iprogrammer
UNец
 
Сообщения: 41
Зарегистрирован: 25 июн 2016, 07:10

Re: Почему не работает событие после перекомпиляции?

Сообщение seaman 03 июл 2018, 19:50

Никто ничего не сбрасывает, все сохраняется в нужных местах)))

Это неверно - сбрасывает.
+ Статические поля не сериализуются...
seaman
Адепт
 
Сообщения: 7530
Зарегистрирован: 24 янв 2011, 12:32
Откуда: Самара

Re: Почему не работает событие после перекомпиляции?

Сообщение iprogrammer 03 июл 2018, 23:17

seaman писал(а):
Никто ничего не сбрасывает, все сохраняется в нужных местах)))

Это неверно - сбрасывает.
+ Статические поля не сериализуются...

ну статику это понятно, как константы и поля readonly
iprogrammer
UNец
 
Сообщения: 41
Зарегистрирован: 25 июн 2016, 07:10

Re: Почему не работает событие после перекомпиляции?

Сообщение getAlex 12 июл 2018, 14:04

https://docs.unity3d.com/ru/current/Man ... ation.html
Очевидно, что проблема в сериализации. Далеко не всё можно сериализовать. К тому же, там много правил как именно нужно сериализовать свои объекты.
getAlex
Адепт
 
Сообщения: 1698
Зарегистрирован: 10 авг 2013, 18:30

Re: Почему не работает событие после перекомпиляции?

Сообщение iprogrammer 26 июл 2018, 09:31

getAlex писал(а):https://docs.unity3d.com/ru/current/Manual/script-Serialization.html
Очевидно, что проблема в сериализации. Далеко не всё можно сериализовать. К тому же, там много правил как именно нужно сериализовать свои объекты.


Спасибо за комментарии, но обо всём, в т.ч. почему в моем случае не работали события я уже написал выше: http://unity3d.ru/distribution/viewtopic.php?f=18&t=47822#p292192
Так что, по сути, тема закрыта. Просто я не знаю можно ли её тут закрыть в принципе)

!!Тема Закрыта.
iprogrammer
UNец
 
Сообщения: 41
Зарегистрирован: 25 июн 2016, 07:10


Вернуться в Скрипты

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 10