6
\$\begingroup\$

There's a simple guide on the Firebase's website on how to implement the AdMob's rewarded video: https://firebase.google.com/docs/admob/unity/rewarded-video

Let's take some code:

public void OnVideoAdComplete(object sender, Reward reward){
    SoundMgr.Instance().PlayRewardSound();
    // double coins etc
}

Let's plug it in:

private void LoadVideoAd(){
    videoAd = RewardBasedVideoAd.Instance;

    AdRequest request = new AdRequest.Builder().AddTestDevice("XXXXXXXXXXXXX").Build();
    videoAd.LoadAd(request, androidVideoAdID);

    if(!videoCallbackSet){
        videoCallbackSet = true;
        videoAd.OnAdRewarded += OnVideoAdComplete;
    }
}

And now let's get a ThreadException cause we played a sound on the other thread:

Unity   : Function SoundChannelStopList::~SoundChannelStopList() may only be called from main thread!

Unity   : ExecutionEngineException: SIGILL
Unity   :   at SoundManager.PlaySuccess1Sound () [0x00000] in <filename unknown>:0
Unity   :   at Game.OnVideoAdClose () [0x00000] in <filename unknown>:0
Unity   :   at AdManager.OnVideoAdClose (System.Object sender, System.EventArgs args) [0x00000] in <filename unknown>:0
Unity   :   at GoogleMobileAds.Api.RewardBasedVideoAd.<RewardBasedVideoAd>m__4 (System.Object sender, System.EventArgs args) [0x00000] in <filename unknown>:0
Unity   :   at (wrapper delegate-invoke) 
System.EventHandler`1<System.EventArgs>:invoke_void__this___object_EventArgs (object,System.EventArgs)
Unity   :   at GoogleMobileAds.Android.RewardBasedVideoAdClient.onAdClosed () [0x00000] in <filename unknown>:0
Unity   :   at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0
Unity   : Rethrow as TargetInvocationException: GoogleMobileAds.Android.RewardBasedVideoAdClient.onAdClosed()
Unity   :   at UnityEngine.AndroidJavaPro

How is this possible that it's year 2017 and the most leading game engine's plugin is runnning it's callbacks without some sort of App.PostRunnableOnMainThread()?

Is there any workaround on this or we need to create an incredible pattern like:

update(){
    if(adFinished)
        //give rewards
    }

I'd be grateful for any tips! :)

\$\endgroup\$
1
\$\begingroup\$

If you want to work with Unity and threads, you'll need to implement 2 features that are unfortunately absent from Unity: A thread pool and a main thread dispatcher.

using System;
using System.Collections.Generic;
using System.Threading;

public class UnityThreadPool : UnityEngine.MonoBehaviour
{       
    Deque<Action> mMainThreadWaitQueue;
    Deque<Action> mCurrentlyProcessing;
    static UnityThreadPool sInstance;
    Pool mPool;

    public void EnqueueOnMain(Action task) { lock (mMainThreadWaitQueue) mMainThreadWaitQueue.AddToBack(task); }
    public void EnqueueOnWorker(Action task) { mPool.QueueTask(task); }
    public static UnityThreadPool Instance {
        get {
            if (sInstance == null)
                sInstance = new UnityEngine.GameObject("ThreadPool").AddComponent<UnityThreadPool>();
            return sInstance;
        }
    }

    void Awake()
    {
        UnityEngine.Debug.Assert(sInstance == null,"Multiple ThreadPools detected.");
        DontDestroyOnLoad(transform.gameObject);
        mPool = new Pool(UnityEngine.SystemInfo.processorCount); 
        mMainThreadWaitQueue = new Deque<Action>(10);
        mCurrentlyProcessing = new Deque<Action>(10);
    }
    void Update()
    {
        lock (mMainThreadWaitQueue) {
            if (mMainThreadWaitQueue.Count == 0)
                return;
            UnityEngine.Debug.Assert(mCurrentlyProcessing.Count == 0);
            mCurrentlyProcessing.InsertRange(0,mMainThreadWaitQueue);
            mMainThreadWaitQueue.Clear();
        }           
        var watch = System.Diagnostics.Stopwatch.StartNew(); // this is the main thread, track time so we don't block it for too long
        while (mCurrentlyProcessing.Count !=  0){
            var action = mCurrentlyProcessing.RemoveFromFront();
            try { action(); } catch (System.Exception e) { UnityEngine.Debug.LogError("Unhandled Exception in MainThread task: " + e.ToString()); }
            if (watch.ElapsedMilliseconds > 10) {
                lock (mMainThreadWaitQueue) { mMainThreadWaitQueue.InsertRange(0,mCurrentlyProcessing); }
                mCurrentlyProcessing.Clear();
                break;
            }
        }
    }        
    void OnDestroy() { mPool.Dispose(); }
}

For an example implementation of Pool see: https://stackoverflow.com/a/436552/1612743

But chances are Unity will support the basic .Net threadpool in an upcoming version. If they do you won't need that part.

\$\endgroup\$
0
\$\begingroup\$

You can set a boolean value in the callback function as true, and in the main thread have an Update() function polling that value. Also just to be sure, you could use a lock(). Like this:

public class AdMobVideoRewarder : MonoBehaviour
{
    object threadLock = new object();
    bool videoShown;
    public void OnVideoAdComplete(object sender, Reward reward){
        lock(threadLock) videoShown = true;
    }
    void Update()
    {
        lock(threadLock)
        {
            if(videoShown)
            {
                videoshown = false;
                SoundMgr.Instance().PlayRewardSound();
            }
        }
    }
}

As a side note you should not have too complex code inside a lock() code block in the callback function as it will stall the main thread from executing. Also the lock may be too much in this case as it's only for setting one boolean value.

\$\endgroup\$
  • \$\begingroup\$ Locking in the Update() method in each frame to set one value is the worst programming practice I've ever seen. For now, as I can see, this is really a recommended way (by developers, not Unity). I'll post a bug/feature request or something. Thanks for your feedback. I'll wait for some responses and accept your answer soon. \$\endgroup\$ – Jacob May 13 '17 at 15:11
  • \$\begingroup\$ It's the Unity way of doing things. You just need to hack yourself through all the stupid obstacles the engine provides for you. For this reason I wouldn't recommend Unity for production, only for prototyping. \$\endgroup\$ – Lasse May 13 '17 at 15:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.