Unity uses Xlua to encounter pits

Source: Internet
Author: User
Tags lua

When we use Xlua as a solution integrated with LUA in unity, we have a problem when we use a control in Lua to bind the corresponding event (such as the button's OnClick event), and the Xlua binding event is implemented with a delegate, Specific code can view the Xlua code. When the program exits, Xlua checks that the corresponding delegate has been properly released, and throws an exception if it is not released. The code is as shown in the table:

1          Public Virtual voidDispose (BOOLdispose)2         {3 #ifThread_safe | | Hotfix_enable4             Lock(Luaenvlock)5             {6 #endif7                 if(disposed)return;8 Tick ();9 Ten                 if(!Translator. Alldelegatebridgereleased ()) One                 { A                     Throw NewInvalidOperationException ("try to dispose a luaenv with C # callback!"); -                 } -  the Luaapi.lua_close (L); -  - ObjectTranslatorPool.Instance.Remove (L); -Translator =NULL; +  -RawL =IntPtr.Zero; +  Adisposed =true; at #ifThread_safe | | Hotfix_enable -             } - #endif -}

This means that we have not released the corresponding delegate. So we need to make sure that all the delegates are released correctly before the program exits. The scenario is broadly as follows, where each UI corresponds to an instance, creating an anonymous function when the control is bound, which is used by the control to erase the event bound by the control, and to put the anonymous function in an array. Invoking a function when the UI is destroyed (for example, we call destroy), this function is responsible for some cleanup work, including traversing the array of anonymous functions mentioned earlier and calling them all. This removes the reference to the Xlua-generated delegate. When the program exits and triggers the GC, the delegate is released, so the Xlua check is no problem.

1 function Uiutils:addbuttononclick (auiinstance, Abutton, Afunc)2 AButton.onClick.AddListener (3 function ()4 Afunc (auiinstance)5 end)6 7     //add closures to a table for subsequent calls8 Table.insert (Auiinstance.unregisterwidgetclousures,9 function ()Ten aButton.onClick:RemoveAllListeners () One end) A  -End

You may find that the problem has been solved, but if you come here, you will not have this article. The problem is that the call will then throw an exception when the program exits. As a normal way to do so, after some experiments have found that as long as the control has not been touched, then you can exit normally, if you touch it will throw an exception. At first the suspicion was Xlua, but it was not a problem to look at the code to determine it. At this point, it might be possible for unity to cache this delegate, although I've cleared it off, but unity might have been cached inside. At first, I did not pay attention to this problem, but thought of another way to direct the control corresponding to the event to the black kiln. The sample code looks like this:

1 function Uiutils:addbuttononclick (auiinstance, Abutton, Afunc)2 AButton.onClick.AddListener (3 function ()4 Afunc (auiinstance)5 end)6 7     //add closures to a table for subsequent calls8 Table.insert (Auiinstance.unregisterwidgetclousures,9 function ()TenAbutton.onclick =Nil One end) A  -End

This solves the problem. But later we found that we had to reuse the UI because of the rules we reused (the UI's C # object was not recycled but would be recycled, but the Lua object would be recycled), and this place would be a problem. The next time we re-use the UI, because it's empty, then there's a problem with it. We have also thought of other ways to solve it, but the total feeling is that it destroys the original simple structure. It's not good to do that. This is the time to see where unity is going wrong, but fortunately it quickly discovers the problem. We opened UnityEngine.dll with Ilspy and looked at the code of Unityevent, and found a simple optimization in its base class, which led to the above problem. Let's look at the code snippet:

1  Public Abstract class Unityeventbase:iserializationcallbackreceiver 2 {3     Private invokablecalllist m_calls; 4 }

Unity uses this to save the calling function, so let's take a look at its specific implementation fragment:

1 namespaceunityengine.events2 {3     Internal classinvokablecalllist4     {5         Private ReadOnlyList<baseinvokablecall> M_persistentcalls =NewList<baseinvokablecall>();6 7         Private ReadOnlyList<baseinvokablecall> M_runtimecalls =NewList<baseinvokablecall>();8 9         Private ReadOnlyList<baseinvokablecall> M_executingcalls =NewList<baseinvokablecall>();Ten  One         Private BOOLM_needsupdate =true; A  -          Public voidAddListener (Baseinvokablecall call) -         { the              This. M_runtimecalls.add (call); -              This. m_needsupdate =true; -         } -  +          Public voidRemoveListener (ObjectTargetobj, MethodInfo method) -         { +list<baseinvokablecall> list =NewList<baseinvokablecall>(); A              for(inti =0; I < This. M_runtimecalls.count; i++) at             { -                 if( This. M_runtimecalls[i]. Find (Targetobj, method)) -                 { -List. ADD ( This. M_runtimecalls[i]); -                 } -             } in              This. M_runtimecalls.removeall (NewPredicate<baseinvokablecall>(list. Contains)); -              This. m_needsupdate =true; to         } +  -          Public voidClear () the         { *              This. M_runtimecalls.clear (); $              This. m_needsupdate =true;Panax Notoginseng         } -  the          Public voidInvoke (Object[] Parameters) +         { A             if( This. M_needsupdate) the             { +                  This. M_executingcalls.clear (); -                  This. M_executingcalls.addrange ( This. m_persistentcalls); $                  This. M_executingcalls.addrange ( This. m_runtimecalls); $                  This. m_needsupdate =false; -             } -              for(inti =0; I < This. M_executingcalls.count; i++) the             { -                  This. M_executingcalls[i]. Invoke (parameters);Wuyi             } the         } -     } Wu}
We see m_runtimecalls This variable, what is it used for? is to make an optimization, in order to update it only after the addition or removal of the listener. There is no problem with the original design of unity itself. However, we look at the RemoveListener or clear when the value of the m_runtimecalls is not clear, supposedly it is clear () should be cleared. So there is the problem that we mentioned earlier. Knowing the reason, there are two workarounds:
    1. Direct must UnityEngine.dll code, because we do not have the source, so only through some work to change. But this brings me to the question of replacing the modified DLL with everyone in the development group, and the other problem is that if you upgrade unity, it will cause unnecessary trouble. So the plan was abandoned.
    2. We can see that although clear () does not call M_executingcalls.clear (), we can call the Invoke () function again, and this time it will clear the contents of the M_executingcalls. At this point, there is no object referencing the Xlua generated by the delegate. The scheme is still relatively good at the moment. Because after all, the overhead of multiple invocations is acceptable.

The code then becomes the following code example:

1 function Uiutils:addbuttononclick (auiinstance, Abutton, Afunc)2 AButton.onClick.AddListener (3 function ()4 Afunc (auiinstance)5 end)6 7     //add closures to a table for subsequent calls8 Table.insert (Auiinstance.unregisterwidgetclousures,9 function ()Ten aButton.onClick:RemoveAllListeners () One Abutton.onclick () A end) -  -End

Well, the problem has been solved perfectly. Of course, we can simply comment out the place where the exception is thrown, but this is certainly not the correct way to solve the problem. Of course, if you encounter this problem and have a better plan, you can discuss it together.

Unity uses Xlua to encounter pits

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.