/**
* Ashita SDK - Copyright (c) 2023 Ashita Development Team
* Contact: https://www.ashitaxi.com/
* Contact: https://discord.gg/Ashita
*
* This file is part of Ashita.
*
* Ashita is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Ashita is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Ashita. If not, see .
*/
#ifndef ASHITA_SDK_THREADING_H_INCLUDED
#define ASHITA_SDK_THREADING_H_INCLUDED
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
#include
#include
#include
namespace Ashita::Threading
{
/**
* Implements a basic synchronization object backed by Win32 event API.
*
*/
class Event
{
HANDLE m_EventHandle;
public:
explicit Event(const bool manualReset = true)
{
this->m_EventHandle = ::CreateEventA(nullptr, manualReset, FALSE, nullptr);
}
~Event(void)
{
if (this->m_EventHandle != nullptr)
::CloseHandle(this->m_EventHandle);
this->m_EventHandle = nullptr;
}
/**
* Resets the event to its default state.
*
* @return {bool} True on success, false otherwise.
*/
bool Reset(void) const
{
if (this->m_EventHandle == nullptr)
return false;
return ::ResetEvent(this->m_EventHandle) ? true : false;
}
/**
* Sets the event to the signaled state.
*
* @return {bool} True on success, false otherwise.
*/
bool Raise(void) const
{
if (this->m_EventHandle == nullptr)
return false;
return ::SetEvent(this->m_EventHandle) ? true : false;
}
/**
* Checks and returns if the event is signaled.
*
* @param {bool} True if signaled, false otherwise.
*/
bool IsSignaled(void) const
{
if (this->m_EventHandle == nullptr)
return false;
return ::WaitForSingleObject(this->m_EventHandle, 0) == WAIT_OBJECT_0;
}
/**
* Waits for the event to be signaled.
*
* @param {uint32_t} milliseconds - The amount of time, in milliseconds, to wait.
* @return {bool} True on success, false otherwise.
*/
bool WaitFor(const uint32_t milliseconds) const
{
return ::WaitForSingleObject(this->m_EventHandle, milliseconds) == WAIT_OBJECT_0;
}
};
/**
* Implements a basic locking mechanism backed by a critical section.
*
* For faster and modern locking, it is recommended to use the newer std::lock_guard
* scheme with a std::mutex object instead. (Performance will vary based on compiler
* versions. Recommended for use with VC++ 2015 Update 3 or newer.)
*/
class LockableObject
{
CRITICAL_SECTION m_CriticalSection;
// Delete Copy and Assignment Operators
LockableObject(LockableObject const&) = delete; // Delete Copy Constructor
LockableObject(LockableObject&&) = delete; // Delete Move Constructor
LockableObject& operator=(LockableObject const&) = delete; // Delete Copy Assignment Constructor
LockableObject& operator=(LockableObject&&) = delete; // Delete Move Assignment Constructor
public:
LockableObject(void)
{
::InitializeCriticalSection(&this->m_CriticalSection);
}
~LockableObject(void)
{
::DeleteCriticalSection(&this->m_CriticalSection);
}
/**
* Locks the critical section.
*/
void Lock(void)
{
::EnterCriticalSection(&this->m_CriticalSection);
}
/**
* Unlocks the critical section.
*/
void Unlock(void)
{
::LeaveCriticalSection(&this->m_CriticalSection);
}
};
/**
* Thread Priority Enumeration
*/
enum class ThreadPriority : int32_t
{
Lowest = -2,
BelowNormal = -1,
Normal = 0,
AboveNormal = 1,
Highest = 2
};
/**
* Implements a basic threading object. Backed by events using the above Event class object.
*
* This class is based on code from a private threading library:
* Copyright(C) 1995-2006 Anton S. Yemelyanov
*
* Full permission was granted to use his code via email.
* This is losely based on v1.4 of his code from GAPI_Thread.hpp/.cpp.
*/
class Thread
{
HANDLE m_Handle;
DWORD m_Id;
Ashita::Threading::Event m_EventStart;
Ashita::Threading::Event m_EventEnd;
public:
Thread(void)
: m_Handle(nullptr)
, m_Id(0)
, m_EventStart(true)
, m_EventEnd(true)
{}
virtual ~Thread(void)
{
if (this->m_Handle != nullptr)
this->Stop();
}
/**
* Thread entry function. (Inheriting classes must implement this.)
*
* @return {uint32_t} Thread specific return value.
*/
virtual uint32_t ThreadEntry(void) = 0;
/**
* Internal thread entry used to signal the thread and run the inheriting entry.
*
* @return {uint32_t} Thread specific return value.
*/
uint32_t InternalEntry(void)
{
if (this->IsTerminated())
return 0;
// Reset and raise the events..
this->m_EventEnd.Reset();
::Sleep(10);
this->m_EventStart.Raise();
// Run the thread..
return this->ThreadEntry();
}
/**
* Starts the thread.
*/
void Start(void)
{
this->m_EventStart.Reset();
this->m_EventEnd.Reset();
this->m_Handle = ::CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)ThreadCallback, this, 0, &this->m_Id);
}
/**
* Stops the thread.
*/
void Stop(void)
{
// Raise the end signal..
this->RaiseEnd();
// Wait for the thread to end then cleanup..
if (this->WaitFor(INFINITE))
{
::CloseHandle(this->m_Handle);
this->m_Handle = nullptr;
this->m_Id = 0;
}
}
/**
* Returns if the end event is signaled telling the thread to terminate.
*/
bool IsTerminated(void) const
{
return this->m_EventEnd.IsSignaled();
}
/**
* Waits for the given amount of time for the thread to response.
*
* @param {uint32_t} milliseconds - The amount of time, in milliseconds, to wait.
* @return {bool} True on success, false otherwise.
*/
bool WaitFor(const uint32_t milliseconds = INFINITE) const
{
if (this->m_Handle == nullptr)
return false;
return ::WaitForSingleObject(this->m_Handle, milliseconds) != WAIT_TIMEOUT;
}
/**
* Returns the threads current priority.
*
* @return {ThreadPriority} The threads priority.
*/
ThreadPriority GetPriority(void) const
{
if (this->m_Handle == nullptr)
return ThreadPriority::Normal;
return (ThreadPriority)::GetThreadPriority(this->m_Handle);
}
/**
* Sets the threads priority.
*
* @param {ThreadPriority} p - The new priority to set the thread to.
*/
void SetPriority(ThreadPriority p) const
{
if (this->m_Handle != nullptr)
::SetThreadPriority(this->m_Handle, (int32_t)p);
}
/**
* Signals the end event telling the thread to stop.
*/
void RaiseEnd(void) const
{
this->m_EventEnd.Raise();
}
/**
* Resets the end event signal.
*/
void ResetEnd(void) const
{
this->m_EventEnd.Reset();
}
/**
* Returns the thread handle.
*
* @return {HANDLE} The thread handle.
*/
HANDLE GetHandle(void) const
{
return this->m_Handle;
}
/**
* Returns the thread id.
*
* @return {DWORD} The thread id.
*/
DWORD GetId(void) const
{
return this->m_Id;
}
/**
* Returns the thread exit code.
*
* @return {DWORD} The thread exit code.
*/
DWORD GetExitCode(void) const
{
if (this->m_Handle == nullptr)
return 0;
DWORD exitCode = 0;
::GetExitCodeThread(this->m_Handle, &exitCode);
return exitCode;
}
private:
/**
* Internal thread callback to invoke the inheriting parents handler.
*
* @param {LPVOID} param - The thread object passed to the callback.
* @return {uint32_t} The internal threads return value, 0 otherwise.
*/
static uint32_t __stdcall ThreadCallback(const LPVOID param)
{
if (const auto thread = (Ashita::Threading::Thread*)param; thread != nullptr)
return thread->InternalEntry();
return 0;
}
};
} // namespace Ashita::Threading
#endif // ASHITA_SDK_THREADING_H_INCLUDED