diff --git a/buildbot/PackageScript b/buildbot/PackageScript index 8b428e3..4894e48 100644 --- a/buildbot/PackageScript +++ b/buildbot/PackageScript @@ -9,7 +9,9 @@ builder.SetBuildFolder('package') folder_list = [ 'addons/sourcemod/extensions', 'addons/sourcemod/gamedata', - 'addons/sourcemod/configs' + 'addons/sourcemod/configs', + 'addons/sourcemod/scripting', + 'addons/sourcemod/scripting/include', ] # Are we build a x86_64 extension ? @@ -42,6 +44,20 @@ def CopyDirContent(src, dest): if item.is_file(): builder.AddCopy(item.path, dest_entry) +# Include files +CopyFiles('scripting/include', 'addons/sourcemod/scripting/include', + [ + 'accelerator.inc', + ] +) + +# Example files +CopyFiles('scripting', 'addons/sourcemod/scripting', + [ + 'accelerator_example.sp', + ] +) + # Copy binaries. for task in Accelerator.extension: if task.target.arch == 'x86_64': diff --git a/extension/AMBuilder b/extension/AMBuilder index 1e6f25f..0d6126d 100644 --- a/extension/AMBuilder +++ b/extension/AMBuilder @@ -7,6 +7,8 @@ project = builder.LibraryProject('accelerator.ext') project.sources = [ 'extension.cpp', 'MemoryDownloader.cpp', + 'forwards.cpp', + 'natives.cpp', os.path.join(Accelerator.sm_root, 'public', 'smsdk_ext.cpp') ] diff --git a/extension/extension.cpp b/extension/extension.cpp index 2c75c96..d4a8f75 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -27,6 +27,8 @@ #include #include "MemoryDownloader.h" +#include "forwards.h" +#include "natives.h" #if defined _LINUX #include "client/linux/handler/exception_handler.h" @@ -464,6 +466,8 @@ class UploadThread: public IThread count++; g_pSM->LogError(myself, "Accelerator uploaded crash dump: %s", response); if (log) fprintf(log, "Uploaded crash dump: %s\n", response); + UploadedCrash crash{ response }; + g_accelerator.StoreUploadedCrash(crash); } else { failed++; g_pSM->LogError(myself, "Accelerator failed to upload crash dump: %s", response); @@ -495,6 +499,7 @@ class UploadThread: public IThread log = nullptr; } + g_accelerator.MarkAsDoneUploading(); rootconsole->ConsolePrint("Accelerator upload thread finished. (%d skipped, %d uploaded, %d failed)", skip, count, failed); } @@ -1080,6 +1085,25 @@ class UploadThread: public IThread } } uploadThread; +class SourcePawnNotifyThread : public IThread +{ +public: + + void RunThread(IThreadHandle* pHandle) { + for (;;) { + // Wait until OnMapStart is called once, this should be enough delay to make sure plugins are loaded. + if (g_accelerator.IsMapStarted() && g_accelerator.IsDoneUploading()) { + extforwards::CallOnDoneUploadingForward(); + break; + } + } + } + + void OnTerminate(IThreadHandle* pHandle, bool cancel) { + } + +} spNotifyThread; + class VFuncEmptyClass {}; const char *GetCmdLine() @@ -1117,6 +1141,11 @@ const char *GetCmdLine() return (const char *)(reinterpret_cast(cmdline)->*u.mfpnew)(); } +Accelerator::Accelerator() : + m_doneuploading(false), m_maphasstarted(false) +{ +} + bool Accelerator::SDK_OnLoad(char *error, size_t maxlength, bool late) { sharesys->AddDependency(myself, "webternet.ext", true, true); @@ -1142,6 +1171,7 @@ bool Accelerator::SDK_OnLoad(char *error, size_t maxlength, bool late) strncpy(crashGameDirectory, g_pSM->GetGameFolderName(), sizeof(crashGameDirectory) - 1); threader->MakeThread(&uploadThread); + threader->MakeThread(&spNotifyThread); // This thread waits for accelator to be done uploading and for the first OnMapStart call, then fires a SourceMod forward do { char gameconfigError[256]; @@ -1321,6 +1351,7 @@ bool Accelerator::SDK_OnLoad(char *error, size_t maxlength, bool late) void Accelerator::SDK_OnUnload() { + extforwards::Shutdown(); plsys->RemovePluginsListener(this); #if defined _LINUX @@ -1336,9 +1367,21 @@ void Accelerator::SDK_OnUnload() delete handler; } +void Accelerator::SDK_OnAllLoaded() +{ + extforwards::Init(); + sharesys->RegisterLibrary(myself, "accelerator"); + + natives::Setup(m_natives); + m_natives.push_back({ nullptr, nullptr }); // SM requires this to signal the end of the native info array + + sharesys->AddNatives(myself, m_natives.data()); +} + void Accelerator::OnCoreMapStart(edict_t *pEdictList, int edictCount, int clientMax) { strncpy(crashMap, gamehelpers->GetCurrentMap(), sizeof(crashMap) - 1); + m_maphasstarted.store(true); } /* 010 Editor Template @@ -1499,3 +1542,25 @@ void Accelerator::OnPluginUnloaded(IPlugin *plugin) SerializePluginContexts(); } + +void Accelerator::StoreUploadedCrash(UploadedCrash& crash) +{ + std::lock_guard lock(m_uploadedcrashes_mutex); + m_uploadedcrashes.push_back(std::move(crash)); +} + +const UploadedCrash* Accelerator::GetUploadedCrash(int element) const +{ + std::lock_guard lock(m_uploadedcrashes_mutex); + if (element < 0 || element >= static_cast(m_uploadedcrashes.size())) { + return nullptr; + } + + return &m_uploadedcrashes[element]; +} + +cell_t Accelerator::GetUploadedCrashCount() const +{ + std::lock_guard lock(m_uploadedcrashes_mutex); + return static_cast(m_uploadedcrashes.size()); +} diff --git a/extension/extension.h b/extension/extension.h index d9495ef..0412a7c 100644 --- a/extension/extension.h +++ b/extension/extension.h @@ -26,14 +26,36 @@ * @brief Accelerator extension code header. */ +#include +#include +#include #include "smsdk_ext.h" +/** + * @brief Represents a crash that has been successfully uploaded to Accelerator's backend + */ +class UploadedCrash +{ +public: + UploadedCrash(const char* response) : + m_httpresponse(response) + { + } + + const std::string& GetHTTPResponse() const { return m_httpresponse; } + +private: + std::string m_httpresponse; // HTTP response from the crash upload in free form text. +}; + /** * @brief Sample implementation of the SDK Extension. */ class Accelerator : public SDKExtension, IPluginsListener { public: // SDKExtension + Accelerator(); + /** * @brief This is called after the initial loading sequence has been processed. * @@ -49,6 +71,11 @@ public: // SDKExtension */ virtual void SDK_OnUnload(); + /** + * @brief This is called once all known extensions have been loaded. + */ + virtual void SDK_OnAllLoaded(); + /** * @brief Called on server activation before plugins receive the OnServerLoad forward. * @@ -68,6 +95,47 @@ public: // IPluginsListener * @brief Called when a plugin is unloaded (only if fully loaded). */ virtual void OnPluginUnloaded(IPlugin *plugin); + + /** + * @brief Stores an uploaded crash into the vector. + * @param crash Uploaded crash instance to store. + */ + void StoreUploadedCrash(UploadedCrash& crash); + /** + * @brief Retrieves an uploaded crash from the given element. + * @param element Vector element (index) to read. + * @return Upload crash if found or NULL if out of bounds. + */ + const UploadedCrash* GetUploadedCrash(int element) const; + /** + * @brief Returns the number of uploaded crashes (for SourcePawn plugins). + * @return Number of uploaded crashes. + */ + cell_t GetUploadedCrashCount() const; + /** + * @brief Signals the extension that uploading is done. + */ + void MarkAsDoneUploading() { m_doneuploading.store(true); } + /** + * @brief Is Accelerator done uploading crashes. + * @return Returns true if yes, false otherwise. + */ + bool IsDoneUploading() const { return m_doneuploading.load(); } + /** + * @brief Has the 'OnMapStart' function called at least once. + * @return True if yes, false otherwise. + */ + bool IsMapStarted() const { return m_maphasstarted.load(); } + +private: + std::vector m_uploadedcrashes; // Vector of uploaded crashes + std::vector m_natives; // Vector of SourcePawn natives + mutable std::mutex m_uploadedcrashes_mutex; // mutex for accessing the m_uploadedcrashes vector + std::atomic_bool m_doneuploading; // Signals that Accelerator is done uploading crashes. + std::atomic_bool m_maphasstarted; // Signals that OnMapStart has been called at least once. }; +// Expose the extension singleton. +extern Accelerator g_accelerator; + #endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/extension/forwards.cpp b/extension/forwards.cpp new file mode 100644 index 0000000..a1fbe3b --- /dev/null +++ b/extension/forwards.cpp @@ -0,0 +1,36 @@ +#include +#include +#include "extension.h" +#include "forwards.h" + + +static SourceMod::IForward* s_ondoneuploadingforward = nullptr; + +static void OnDoneUploadingCallback(void* data) +{ + if (s_ondoneuploadingforward) { + s_ondoneuploadingforward->Execute(); + } +} + + +void extforwards::Init() +{ + s_ondoneuploadingforward = forwards->CreateForward("Accelerator_OnDoneUploadingCrashes", SourceMod::ExecType::ET_Ignore, 0, nullptr); +} + +void extforwards::Shutdown() +{ + if (s_ondoneuploadingforward) { + forwards->ReleaseForward(s_ondoneuploadingforward); + s_ondoneuploadingforward = nullptr; + } +} + +void extforwards::CallOnDoneUploadingForward() +{ + // It's obligatory to use AddFrameAction here because SourcePawn forwards can only be called from the server's main thread. + // AddFrameAction will do this for us and is also thread safe. + smutils->AddFrameAction(OnDoneUploadingCallback, nullptr); +} + diff --git a/extension/forwards.h b/extension/forwards.h new file mode 100644 index 0000000..4f0ea1e --- /dev/null +++ b/extension/forwards.h @@ -0,0 +1,16 @@ +#ifndef _INCLUDE_FORWARDS_H_ +#define _INCLUDE_FORWARDS_H_ + +namespace extforwards +{ + // Initialize the sourcepawn forwards. + void Init(); + // Shutdown the sourcepawn forwards. + void Shutdown(); + // Calls the on done uploadind forward. (thread safe) + void CallOnDoneUploadingForward(); +} + + + +#endif // !_INCLUDE_FORWARDS_H_ diff --git a/extension/natives.cpp b/extension/natives.cpp new file mode 100644 index 0000000..f64c193 --- /dev/null +++ b/extension/natives.cpp @@ -0,0 +1,54 @@ +#include +#include +#include "extension.h" +#include "natives.h" + +static cell_t Native_GetUploadedCrashCount(IPluginContext* context, const cell_t* params) +{ + return g_accelerator.GetUploadedCrashCount(); +} + +static cell_t Native_IsDoneUploadingCrashes(IPluginContext* context, const cell_t* params) +{ + if (g_accelerator.IsDoneUploading()) { + return 1; + } + + return 0; +} + +static cell_t Native_GetCrashHTTPResponse(IPluginContext* context, const cell_t* params) +{ + if (!g_accelerator.IsDoneUploading()) { + context->ReportError("Wait until accelerator is done uploading crashes before accessing crash information!"); + return 0; + } + + int element = static_cast(params[1]); + const UploadedCrash* crash = g_accelerator.GetUploadedCrash(element); + + if (!crash) { + context->ReportError("Crash index %i is invalid!", element); + return 0; + } + + char* buffer; + context->LocalToString(params[2], &buffer); + + size_t maxsize = static_cast(params[3]); + + ke::SafeStrcpy(buffer, maxsize, crash->GetHTTPResponse().c_str()); + + return 0; +} + +void natives::Setup(std::vector& vec) +{ + sp_nativeinfo_t list[] = { + {"Accelerator_GetUploadedCrashCount", Native_GetUploadedCrashCount}, + {"Accelerator_IsDoneUploadingCrashes", Native_IsDoneUploadingCrashes}, + {"Accelerator_GetCrashHTTPResponse", Native_GetCrashHTTPResponse}, + }; + + vec.insert(vec.end(), std::begin(list), std::end(list)); +} diff --git a/extension/natives.h b/extension/natives.h new file mode 100644 index 0000000..f3b78b3 --- /dev/null +++ b/extension/natives.h @@ -0,0 +1,11 @@ +#ifndef _INCLUDE_NATIVES_H_ +#define _INCLUDE_NATIVES_H_ + + +namespace natives +{ + // Called during the load process to setup the extension natives + void Setup(std::vector& vec); +} + +#endif // !_INCLUDE_NATIVES_H_ diff --git a/extension/smsdk_config.h b/extension/smsdk_config.h index 7d7c58e..e034460 100644 --- a/extension/smsdk_config.h +++ b/extension/smsdk_config.h @@ -58,7 +58,7 @@ //#define SMEXT_CONF_METAMOD /** Enable interfaces you want to use here by uncommenting lines */ -//#define SMEXT_ENABLE_FORWARDSYS +#define SMEXT_ENABLE_FORWARDSYS //#define SMEXT_ENABLE_HANDLESYS //#define SMEXT_ENABLE_PLAYERHELPERS //#define SMEXT_ENABLE_DBMANAGER diff --git a/scripting/accelerator_example.sp b/scripting/accelerator_example.sp new file mode 100644 index 0000000..cb10aa8 --- /dev/null +++ b/scripting/accelerator_example.sp @@ -0,0 +1,76 @@ +#include +#include + +#pragma newdecls required +#pragma semicolon 1 + +public Plugin myinfo = +{ + name = "Accelerator Example", + author = "caxanga334", + description = "Example Accelerator natives plugin.", + version = "1.0.0", + url = "https://github.com/asherkin/accelerator" +}; + + +public void OnPluginStart() +{ + RegAdminCmd("sm_listcrashes", Command_ListCrashes, ADMFLAG_RCON, "List all uploaded crash dumps."); + + // Call the forward manually. (For late loading plugins) + if (Accelerator_IsDoneUploadingCrashes()) + { + Accelerator_OnDoneUploadingCrashes(); + } +} + +// This is called when the extension is done uploading crashes +public void Accelerator_OnDoneUploadingCrashes() +{ + LogMessage("Accelerator is done uploading crashes!"); + + int max = Accelerator_GetUploadedCrashCount(); + + if (max == 0) + { + LogMessage("No crashes were uploaded!"); + return; + } + + char buffer[512]; + + for (int i = 0; i < max; i++) + { + Accelerator_GetCrashHTTPResponse(i, buffer, sizeof(buffer)); + LogMessage("Crash #%i: HTTP reponse: \"%s\".", i, buffer); + } +} + +// Admin command to list crashes +Action Command_ListCrashes(int client, int args) +{ + if (!Accelerator_IsDoneUploadingCrashes()) + { + ReplyToCommand(client, "Accelerator is still uploading crashes, please wait!"); + return Plugin_Handled; + } + + int max = Accelerator_GetUploadedCrashCount(); + + if (max == 0) + { + ReplyToCommand(client, "No crashes were uploaded!"); + return Plugin_Handled; + } + + char buffer[512]; + + for (int i = 0; i < max; i++) + { + Accelerator_GetCrashHTTPResponse(i, buffer, sizeof(buffer)); + ReplyToCommand(client, "Crash #%i: HTTP reponse: \"%s\".", i, buffer); + } + + return Plugin_Handled; +} diff --git a/scripting/include/accelerator.inc b/scripting/include/accelerator.inc new file mode 100644 index 0000000..6394b29 --- /dev/null +++ b/scripting/include/accelerator.inc @@ -0,0 +1,67 @@ +/** + * Accelerator's SourcePawn include file + */ + +#if defined _accelerator_included + #endinput +#endif +#define _accelerator_included + + +/** + * Called when Accelerator is done uploading crash dumps. + */ +forward void Accelerator_OnDoneUploadingCrashes(); + +/** + * Returns the number of crashes uploaded. + * + * @return Number of crashes uploaded. + */ +native int Accelerator_GetUploadedCrashCount(); + +/** + * Returns if Accelerator is done uploading crashes. + * + * @return True if no more crashes will be uploaded, false if there are still crashes pending upload. + */ +native bool Accelerator_IsDoneUploadingCrashes(); + +/** + * Gets a crash's HTTP response from Accelerator's backend server. + * + * @note The response from the server can be anything and there is no guarantee a crash ID will be present. + * @param index The crash index, starts at 0 and goes up to Accelerator_GetUploadedCrashCount() - 1; + * @param buffer Buffer to store the crash HTTP response. + * @param size Size of the buffer parameter. + * @error Native called before Accelerator_IsDoneUploadingCrashes() returns true or invalid crash index passed. + */ +native void Accelerator_GetCrashHTTPResponse(int index, char[] buffer, int size); + +/** + * Do not edit below this line! + */ +public Extension __ext_accelerator = +{ + name = "accelerator", + file = "accelerator.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public __ext_accelerator_SetNTVOptional() +{ + MarkNativeAsOptional("Accelerator_GetUploadedCrashCount"); + MarkNativeAsOptional("Accelerator_IsDoneUploadingCrashes"); + MarkNativeAsOptional("Accelerator_GetCrashHTTPResponse"); +} +#endif \ No newline at end of file