// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Standard Library
using System;
// Gamekit
using AWS.GameKit.Common;
using AWS.GameKit.Common.Models;
using AWS.GameKit.Runtime.Core;
using AWS.GameKit.Runtime.Utils;
namespace AWS.GameKit.Runtime.FeatureUtils
{
///
/// Wrapper around a singleton for defining a GameKitFeature, for use when calling GameKit features within code
///
public abstract class GameKitFeature : Singleton where T : GameKitFeatureBase, new() { }
///
/// Parent class required by all GameKit features
///
public abstract class GameKitFeatureBase
{
// declare the state of this instance of the feature
public bool IsReady => _isReady;
public abstract FeatureType FeatureType { get; }
protected Threader _threader = new Threader();
private bool _isReady = false;
///
/// Called by the GameKitManager to initialize any requirements of this feature. Called when the is called.
///
public virtual void OnInitialize()
{
// initialize the threader
_threader.Awake();
InitializeFeature();
// declare the state of the singleton as ready
_isReady = true;
}
///
/// Called by the GameKitManager to clean up the feature. Called when the is called.
///
public virtual void OnDispose()
{
// Wait for Threader's pending tasks
_threader.WaitForThreadedWork();
// declare the state of the singleton as not ready
_isReady = false;
// handle any per case requirements before release the feature
DestroyFeature();
// release the feature
GetFeatureWrapperBase().Release();
}
///
/// Called by the GameKitManager to notify features the game has been paused. Called when is called.
///
/// True if the application is paused, else False.
public virtual void OnApplicationPause(bool isPaused)
{
if (_isReady)
{
NotifyPause(isPaused);
}
}
///
/// Called by the GameKitManager to notify features the game has been quit. Called when is called.
///
public virtual void OnApplicationQuit()
{
if (_isReady)
{
NotifyApplicationQuit();
}
}
///
/// Called by the GameKitManager to update any requirements of the feature. Called when is called.
///
public virtual void Update()
{
if (_isReady)
{
// call any feature specific updates
UpdateFeature();
// call all queued callbacks
_threader.Update();
}
}
///
/// For use within a GameKitFeature for calling a feature's API and wrapping inside of a threader call. Also validates if this feature has been initialized.
///
/// GameKit feature API to wrap inside of the thread
/// Action to call once the thread has completed
protected void Call(Action function, Action callback)
{
if (_isReady)
{
_threader.Call(function, callback);
}
else
{
LogCallNotInitialized();
}
}
///
/// For use within a GameKitFeature for calling a feature's API and wrapping inside of a threader call. Also validates if this feature has been initialized.
///
/// GameKit feature API to wrap inside of the thread
/// Action to call once the thread has completed
protected void Call(Func function, Action callback)
{
if (_isReady)
{
_threader.Call(function, callback);
}
else
{
LogCallNotInitialized();
}
}
///
/// For use within a GameKitFeature for calling a feature's API and wrapping inside of a threader call. Also validates if this feature has been initialized.
///
/// GameKit feature API to wrap inside of the thread
/// DESCRIPTION object required to call the API
/// Action to call once the thread has completed
protected void Call(Func function, DESCRIPTION description, Action callback)
{
if (_isReady)
{
_threader.Call(function, description, callback);
}
else
{
LogCallNotInitialized();
}
}
///
/// For use within a GameKitFeature for calling a feature's API and wrapping inside of a threader call. Also validates if this feature has been initialized.
///
/// GameKit feature API to wrap inside of the thread
/// DESCRIPTION object required to call the API
/// Action to call whenever the function needs to return information to the caller
/// Action to call on the completion of this method
protected void Call(
Func, RETURN_RESULT> function,
DESCRIPTION description,
Action callback,
Action onCompleteCallback)
{
if (_isReady)
{
_threader.Call(function, description, callback, onCompleteCallback);
}
else
{
LogCallNotInitialized();
}
}
///
/// For use within a GameKitFeature for calling a feature's API and wrapping inside of a threader call. Also validates if this feature has been initialized.
///
/// GameKit feature API to wrap inside of the thread
/// DESCRIPTION object required to call the API
/// Action to call once the thread has completed
protected void Call(Action function, DESCRIPTION description, Action callback)
{
if (_isReady)
{
_threader.Call(function, description, callback);
}
else
{
LogCallNotInitialized();
}
}
///
/// InitializeFeature is called during the Awake state and is an optional call for the child feature.
///
/// This method does nothing by default. It is not necessary to call `base.InitializeFeature()` when overriding this method.
///
protected virtual void InitializeFeature()
{
// default empty InitializeFeature() call
}
///
/// NotifyPause is called whenever the application is paused or unpaused. This can be useful on platforms such as iOS where the application is
/// suspended before being paused and shutdown code can be unreliable.
///
/// This method does nothing by default. It is not necessary to call `base.NotifyPause()` when overriding this method.
///
///
protected virtual void NotifyPause(bool isPaused)
{
// default empty NotifyPause() call
}
///
/// NotifyApplicationQuit is called whenever the application is quit. NotifyApplicationQuit is called in both Editor and Standalone unlike
/// DestroyFeature which is only called when running on a standalone build.
///
/// This method does nothing by default. It is not necessary to call `base.NotifyPause()` when overriding this method.
///
protected virtual void NotifyApplicationQuit()
{
// default empty NotifyApplicationQuit() call
}
///
/// UpdateFeature is called during the Update state and is an optional call for the child feature.
///
/// This method does nothing by default. It is not necessary to call `base.UpdateFeature()` when overriding this method.
///
protected virtual void UpdateFeature()
{
// default empty UpdateFeature() call
}
///
/// DestroyFeature is called during the OnDestroy state and is an optional call for the child feature. Note DestroyFeature is not called from the Editor.
/// Use NotifyApplicationQuit() for cleanup that should be done for both Editor and Standalone.
///
/// This method does nothing by default. It is not necessary to call `base.DestroyFeature()` when overriding this method.
///
protected virtual void DestroyFeature()
{
// default empty DestroyFeature() call
}
protected abstract GameKitFeatureWrapperBase GetFeatureWrapperBase();
///
/// Helper method for generating a formatted error log when the related child feature is not initialized
///
private void LogCallNotInitialized()
{
string featureMethod = new System.Diagnostics.StackTrace().GetFrame(2).GetMethod().Name;
string featureClass = new System.Diagnostics.StackTrace().GetFrame(2).GetMethod().DeclaringType.FullName;
string callingMethod = new System.Diagnostics.StackTrace().GetFrame(3).GetMethod().Name;
string callingClass = new System.Diagnostics.StackTrace().GetFrame(3).GetMethod().DeclaringType.FullName;
Logging.LogError(
$"The {featureMethod} method of the {featureClass} feature, called in the {callingMethod} method of {callingClass} class" +
$", has not been initialized yet. Please make sure {typeof(GameKitManager).FullName} is attached as a component and enabled.");
}
}
///
/// Abstraction layer around the assignment of the feature wrapper
///
public abstract class GameKitFeatureBase : GameKitFeatureBase where T : GameKitFeatureWrapperBase, new()
{
public T Feature => _feature;
private T _feature = new T();
protected override GameKitFeatureWrapperBase GetFeatureWrapperBase() => _feature;
}
}