From 39134694e2ddcc72768b444e5a8a16b1f219f926 Mon Sep 17 00:00:00 2001 From: Alexander Yee Date: Sat, 7 Dec 2024 01:25:35 -0800 Subject: [PATCH] More work on stream history. Add support for video in error reports. --- Common/Cpp/Exceptions.cpp | 3 + Common/Cpp/Exceptions.h | 11 +++ .../Backends/AudioPassthroughPairQt.cpp | 7 +- .../ErrorReports/ErrorReports.cpp | 80 +++++++++++++++---- .../ErrorReports/ErrorReports.h | 14 +++- .../Exceptions/FatalProgramException.cpp | 61 ++------------ .../Exceptions/FatalProgramException.h | 8 +- .../Exceptions/OperationFailedException.cpp | 33 -------- .../Exceptions/OperationFailedException.h | 3 +- .../Exceptions/ScreenshotException.cpp | 78 +++++++++++++++++- .../Exceptions/ScreenshotException.h | 23 +++++- .../CommonFramework/GlobalSettingsPanel.cpp | 2 +- .../Source/CommonFramework/Globals.cpp | 2 +- .../Recording/StreamHistorySession.cpp | 15 +++- .../Recording/StreamHistorySession.h | 2 +- .../Recording/StreamHistoryTracker_Null.h | 3 +- .../StreamHistoryTracker_RecordOnTheFly.h | 65 ++++++++++++++- .../StreamHistoryTracker_SaveFrames.h | 29 +++++-- .../CommonFramework/Tools/ConsoleHandle.cpp | 4 +- .../CommonFramework/Tools/ConsoleHandle.h | 2 +- .../DevPrograms/TestProgramSwitch.cpp | 4 +- ...ntendoSwitch_MultiSwitchProgramSession.cpp | 7 +- ...tendoSwitch_SingleSwitchProgramSession.cpp | 3 +- .../PokemonSV_ShinyHunt-AreaZeroPlatform.cpp | 5 +- .../TeraRaids/PokemonSV_AutoHostTools.cpp | 20 +++-- .../PokemonSwSh_MaxLair_Run_CaughtScreen.cpp | 7 +- 26 files changed, 331 insertions(+), 160 deletions(-) diff --git a/Common/Cpp/Exceptions.cpp b/Common/Cpp/Exceptions.cpp index 566cbf932..815edb18e 100644 --- a/Common/Cpp/Exceptions.cpp +++ b/Common/Cpp/Exceptions.cpp @@ -11,6 +11,9 @@ namespace PokemonAutomation{ +void Exception::log(Logger& logger) const{ + logger.log(std::string(name()) + ": " + message(), COLOR_RED); +} std::string Exception::message() const{ return ""; } diff --git a/Common/Cpp/Exceptions.h b/Common/Cpp/Exceptions.h index c4ecdbbd9..25fb63e26 100644 --- a/Common/Cpp/Exceptions.h +++ b/Common/Cpp/Exceptions.h @@ -7,12 +7,21 @@ #ifndef PokemonAutomation_Exceptions_H #define PokemonAutomation_Exceptions_H +#include #include "Common/Compiler.h" #include "AbstractLogger.h" namespace PokemonAutomation{ +template +[[noreturn]] void throw_and_log(Logger& logger, Args&&... args){ + ExceptionType exception(std::forward(args)...); + exception.log(logger); + throw exception; +} + + // Definitions: // Catch: To catch the exception using a try-catch. // Consume: To catch the exception and not rethrow it. @@ -22,6 +31,8 @@ namespace PokemonAutomation{ class Exception{ public: virtual ~Exception() = default; + + virtual void log(Logger& logger) const; virtual const char* name() const = 0; virtual std::string message() const; virtual std::string to_str() const; diff --git a/SerialPrograms/Source/CommonFramework/AudioPipeline/Backends/AudioPassthroughPairQt.cpp b/SerialPrograms/Source/CommonFramework/AudioPipeline/Backends/AudioPassthroughPairQt.cpp index 8bef0e747..5a864274e 100644 --- a/SerialPrograms/Source/CommonFramework/AudioPipeline/Backends/AudioPassthroughPairQt.cpp +++ b/SerialPrograms/Source/CommonFramework/AudioPipeline/Backends/AudioPassthroughPairQt.cpp @@ -57,13 +57,12 @@ class AudioPassthroughPairQt::SampleListener final : public AudioFloatStreamList if (parent.m_writer){ parent.m_writer->operator AudioFloatStreamListener&().on_samples(data, frames); } - for (AudioFloatStreamListener* listener : m_parent.m_stream_listeners){ - listener->on_samples(data, frames); - } if (parent.m_fft_runner){ parent.m_fft_runner->on_samples(data, frames); } - + for (AudioFloatStreamListener* listener : m_parent.m_stream_listeners){ + listener->on_samples(data, frames); + } } private: diff --git a/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.cpp b/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.cpp index c2ce5d089..28e7bcbd1 100644 --- a/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.cpp +++ b/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.cpp @@ -15,6 +15,7 @@ #include "CommonFramework/Logging/Logger.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/Options/Environment/ThemeSelectorOption.h" +#include "CommonFramework/Tools/ConsoleHandle.h" #include "ProgramDumper.h" #include "ErrorReports.h" @@ -55,6 +56,11 @@ ErrorReportOption::ErrorReportOption() LockMode::UNLOCK_WHILE_RUNNING, true ) + , VIDEO( + "Include Video:
Include a video leading up to the error. (if possible)", + LockMode::UNLOCK_WHILE_RUNNING, + true + ) , LOGS( "Include Logs:
Include the recent log leading up to the error.", LockMode::UNLOCK_WHILE_RUNNING, @@ -65,20 +71,23 @@ ErrorReportOption::ErrorReportOption() LockMode::UNLOCK_WHILE_RUNNING, true ) -#if 0 , FILES( "Include Other Files:
Include other files that may be helpful for the developers.", LockMode::UNLOCK_WHILE_RUNNING, true ) -#endif { PA_ADD_STATIC(DESCRIPTION); PA_ADD_OPTION(SEND_MODE); PA_ADD_OPTION(SCREENSHOT); + if (PreloadSettings::instance().DEVELOPER_MODE){ + PA_ADD_OPTION(VIDEO); + } PA_ADD_OPTION(LOGS); PA_ADD_OPTION(DUMPS); -// PA_ADD_OPTION(FILES); + if (PreloadSettings::instance().DEVELOPER_MODE){ + PA_ADD_OPTION(FILES); + } } @@ -97,7 +106,8 @@ SendableErrorReport::SendableErrorReport( const ProgramInfo& info, std::string title, std::vector> messages, - const ImageViewRGB32& image + const ImageViewRGB32& image, + ConsoleHandle* console ) : SendableErrorReport() { @@ -126,10 +136,15 @@ SendableErrorReport::SendableErrorReport( } file.write(log.c_str()); file.flush(); - m_files.emplace_back(ERROR_LOGS_NAME); + m_logs_name = ERROR_LOGS_NAME; + } + if (console){ + if (console->save_stream_history(m_directory + "Video.mp4")){ + m_video_name = "Video.mp4"; + } } if (program_dump(logger, m_directory + ERROR_DUMP_NAME)){ - m_files.emplace_back(ERROR_DUMP_NAME); + m_dump_name = ERROR_DUMP_NAME; } } @@ -140,11 +155,6 @@ SendableErrorReport::SendableErrorReport(std::string directory) m_directory += '/'; } - try{ - m_image_owner = ImageRGB32(m_directory + "Image.png"); - m_image = m_image_owner; - }catch (FileException&){} - JsonValue json = load_json_file(m_directory + "Report.json"); const JsonObject& obj = json.to_object_throw(); m_timestamp = obj.get_string_throw("Timestamp"); @@ -165,6 +175,33 @@ SendableErrorReport::SendableErrorReport(std::string directory) ); } } + { + const std::string* image_name = obj.get_string("Screenshot"); + if (image_name){ + try{ + m_image_owner = ImageRGB32(*image_name); + m_image = m_image_owner; + }catch (FileException&){} + } + } + { + const std::string* video_name = obj.get_string("Video"); + if (video_name){ + m_video_name = *video_name; + } + } + { + const std::string* dump_name = obj.get_string("Dump"); + if (dump_name){ + m_dump_name = *dump_name; + } + } + { + const std::string* logs_name = obj.get_string("Logs"); + if (logs_name){ + m_logs_name = *logs_name; + } + } { const JsonArray& files = obj.get_array_throw("Files"); for (const JsonValue& file : files){ @@ -200,8 +237,21 @@ void SendableErrorReport::save(Logger* logger) const{ } report["Messages"] = std::move(messages); } - - m_image.save(m_directory + "Image.png"); + if (m_image){ + std::string image_name = m_directory + "Screenshot.png"; + if (m_image.save(image_name)){ + report["Screenshot"] = std::move(image_name); + } + } + if (!m_video_name.empty()){ + report["Video"] = m_video_name; + } + if (!m_dump_name.empty()){ + report["Dump"] = m_dump_name; + } + if (!m_logs_name.empty()){ + report["Logs"] = m_logs_name; + } JsonArray array; for (const std::string& file : m_files){ @@ -314,6 +364,7 @@ void report_error( std::string title, std::vector> messages, const ImageViewRGB32& image, + ConsoleHandle* console, const std::vector& files ){ if (logger == nullptr){ @@ -326,7 +377,8 @@ void report_error( info, std::move(title), std::move(messages), - image + image, + console ); std::vector full_file_paths; diff --git a/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.h b/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.h index f8c895f4d..038fa9bda 100644 --- a/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.h +++ b/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.h @@ -19,12 +19,16 @@ namespace PokemonAutomation{ +class ConsoleHandle; + + extern const std::string& ERROR_LOGS_NAME; extern const std::string& ERROR_DUMP_NAME; extern const std::string& ERROR_PATH_UNSENT; extern const std::string& ERROR_PATH_SENT; + enum class ErrorReportSendMode{ SEND_AUTOMATICALLY, PROMPT_WHEN_CONVENIENT, @@ -40,9 +44,10 @@ class ErrorReportOption : public GroupOption{ EnumDropdownOption SEND_MODE; BooleanCheckBoxOption SCREENSHOT; + BooleanCheckBoxOption VIDEO; BooleanCheckBoxOption LOGS; BooleanCheckBoxOption DUMPS; -// BooleanCheckBoxOption FILES; + BooleanCheckBoxOption FILES; }; @@ -57,7 +62,8 @@ class SendableErrorReport{ const ProgramInfo& info = ProgramInfo(), std::string title = "", std::vector> messages = {}, - const ImageViewRGB32& image = ImageViewRGB32() + const ImageViewRGB32& image = ImageViewRGB32(), + ConsoleHandle* console = nullptr ); // Deserialize from existing report. @@ -85,6 +91,9 @@ class SendableErrorReport{ std::vector> m_messages; ImageRGB32 m_image_owner; ImageViewRGB32 m_image; + std::string m_logs_name; + std::string m_video_name; + std::string m_dump_name; std::vector m_files; }; @@ -99,6 +108,7 @@ void report_error( std::string title = "", std::vector> messages = {}, const ImageViewRGB32& image = ImageViewRGB32(), + ConsoleHandle* console = nullptr, const std::vector& files = {} ); diff --git a/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.cpp b/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.cpp index e89849ebd..697809863 100644 --- a/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.cpp +++ b/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.cpp @@ -4,71 +4,20 @@ * */ -#include "CommonFramework/ImageTypes/ImageRGB32.h" -#include "CommonFramework/Notifications/ProgramNotifications.h" -#include "CommonFramework/ErrorReports/ErrorReports.h" -//#include "CommonFramework/Tools/ErrorDumper.h" -#include "CommonFramework/Tools/ProgramEnvironment.h" -#include "CommonFramework/Tools/ConsoleHandle.h" #include "FatalProgramException.h" namespace PokemonAutomation{ FatalProgramException::FatalProgramException(ScreenshotException&& e) - : ScreenshotException(e.m_send_error_report, std::move(e.m_message), std::move(e.m_screenshot)) + : ScreenshotException( + e.m_send_error_report, + std::move(e.m_message), + std::move(e.m_screenshot) + ) {} -FatalProgramException::FatalProgramException(ErrorReport error_report, Logger& logger, std::string message) - : ScreenshotException(error_report, std::move(message)) -{ - logger.log(std::string(FatalProgramException::name()) + ": " + m_message, COLOR_RED); -} -FatalProgramException::FatalProgramException(ErrorReport error_report, Logger& logger, std::string message, std::shared_ptr screenshot) - : ScreenshotException(error_report, std::move(message), std::move(screenshot)) -{ - logger.log(std::string(FatalProgramException::name()) + ": " + m_message, COLOR_RED); -} -FatalProgramException::FatalProgramException(ErrorReport error_report, ConsoleHandle& console, std::string message, bool take_screenshot) - : ScreenshotException(error_report, console, std::move(message), take_screenshot) -{ - console.log(std::string(FatalProgramException::name()) + ": " + m_message, COLOR_RED); -} -void FatalProgramException::send_notification(ProgramEnvironment& env, EventNotificationOption& notification) const{ - std::vector> embeds; - if (!m_message.empty()){ - embeds.emplace_back(std::pair("Message:", m_message)); - } - if (m_send_error_report == ErrorReport::SEND_ERROR_REPORT){ - report_error( - &env.logger(), - env.program_info(), - name(), - embeds, - screenshot() - ); -#if 0 - std::string label = name(); - std::string filename = dump_image_alone(env.logger(), env.program_info(), label, *m_screenshot); - send_program_telemetry( - env.logger(), true, COLOR_RED, - env.program_info(), - label, - embeds, - filename - ); -#endif - } - send_program_notification( - env, notification, - COLOR_RED, - "Program Error", - std::move(embeds), "", - screenshot() - ); -} - diff --git a/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.h b/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.h index 9d4621996..4123848d9 100644 --- a/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.h +++ b/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.h @@ -7,7 +7,6 @@ #ifndef PokemonAutomation_FatalProgramException_H #define PokemonAutomation_FatalProgramException_H -#include #include "ScreenshotException.h" namespace PokemonAutomation{ @@ -16,15 +15,10 @@ namespace PokemonAutomation{ // A generic exception that should not be caught outside of infra. class FatalProgramException : public ScreenshotException{ public: + using ScreenshotException::ScreenshotException; FatalProgramException(ScreenshotException&& e); - explicit FatalProgramException(ErrorReport error_report, Logger& logger, std::string message); - explicit FatalProgramException(ErrorReport error_report, Logger& logger, std::string message, std::shared_ptr screenshot); - explicit FatalProgramException(ErrorReport error_report, ConsoleHandle& console, std::string message, bool take_screenshot); virtual const char* name() const override{ return "FatalProgramException"; } - virtual std::string message() const override{ return m_message; } - - virtual void send_notification(ProgramEnvironment& env, EventNotificationOption& notification) const override; }; diff --git a/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.cpp b/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.cpp index 23882ebb7..0bb1f9856 100644 --- a/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.cpp +++ b/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.cpp @@ -32,39 +32,6 @@ OperationFailedException::OperationFailedException(ErrorReport error_report, Con } -void OperationFailedException::send_notification(ProgramEnvironment& env, EventNotificationOption& notification) const{ - std::vector> embeds; - if (!m_message.empty()){ - embeds.emplace_back(std::pair("Message:", m_message)); - } - if (m_send_error_report == ErrorReport::SEND_ERROR_REPORT){ - report_error( - &env.logger(), - env.program_info(), - name(), - embeds, - screenshot() - ); -#if 0 - std::string label = name(); - std::string filename = dump_image_alone(env.logger(), env.program_info(), label, *m_screenshot); - send_program_telemetry( - env.logger(), true, COLOR_RED, - env.program_info(), - label, - embeds, - filename - ); -#endif - } - send_program_notification( - env, notification, - COLOR_RED, - "Program Error", - std::move(embeds), "", - screenshot() - ); -} diff --git a/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.h b/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.h index bfebb5d14..bbe168422 100644 --- a/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.h +++ b/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.h @@ -19,14 +19,13 @@ class FatalProgramException; // These include recoverable errors which can be consumed by the program. class OperationFailedException : public ScreenshotException{ public: + using ScreenshotException::ScreenshotException; explicit OperationFailedException(ErrorReport error_report, Logger& logger, std::string message); explicit OperationFailedException(ErrorReport error_report, Logger& logger, std::string message, std::shared_ptr screenshot); explicit OperationFailedException(ErrorReport error_report, ConsoleHandle& console, std::string message, bool take_screenshot); virtual const char* name() const override{ return "OperationFailedException"; } - virtual std::string message() const override{ return m_message; } - virtual void send_notification(ProgramEnvironment& env, EventNotificationOption& notification) const override; }; diff --git a/SerialPrograms/Source/CommonFramework/Exceptions/ScreenshotException.cpp b/SerialPrograms/Source/CommonFramework/Exceptions/ScreenshotException.cpp index 56fd10ff9..85d689386 100644 --- a/SerialPrograms/Source/CommonFramework/Exceptions/ScreenshotException.cpp +++ b/SerialPrograms/Source/CommonFramework/Exceptions/ScreenshotException.cpp @@ -5,10 +5,17 @@ */ #include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ErrorReports/ErrorReports.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonFramework/Tools/ConsoleHandle.h" +#include "CommonFramework/Tools/ProgramEnvironment.h" #include "ScreenshotException.h" +//#include +//using std::cout; +//using std::endl; + namespace PokemonAutomation{ @@ -32,9 +39,52 @@ ScreenshotException::ScreenshotException(ErrorReport error_report, ConsoleHandle } } } -void ScreenshotException::attach_screenshot(std::shared_ptr screenshot){ +ScreenshotException::ScreenshotException( + ErrorReport error_report, + std::string message, + ConsoleHandle& console +) + : ScreenshotException(error_report, std::move(message)) +{ + m_console = &console; + m_screenshot = console.video().snapshot().frame; + if (m_screenshot == nullptr || !*m_screenshot){ + console.log("Camera returned empty screenshot. Is the camera frozen?", COLOR_RED); + } +} +ScreenshotException::ScreenshotException( + ErrorReport error_report, + std::string message, + ConsoleHandle* console, + ImageRGB32 screenshot +) + : ScreenshotException(error_report, std::move(message)) +{ + m_console = console; + m_screenshot = std::make_shared(std::move(screenshot)); +} +ScreenshotException::ScreenshotException( + ErrorReport error_report, + std::string message, + ConsoleHandle* console, + std::shared_ptr screenshot +) + : ScreenshotException(error_report, std::move(message)) +{ + m_console = console; m_screenshot = std::move(screenshot); } + + +void ScreenshotException::add_console_if_needed(ConsoleHandle& console){ + if (m_console == nullptr){ + m_console = &console; + } + if (!m_screenshot){ + m_screenshot = console.video().snapshot(); + } +} + ImageViewRGB32 ScreenshotException::screenshot() const{ if (m_screenshot){ return *m_screenshot; @@ -44,6 +94,32 @@ ImageViewRGB32 ScreenshotException::screenshot() const{ } +void ScreenshotException::send_notification(ProgramEnvironment& env, EventNotificationOption& notification) const{ + std::vector> embeds; + if (!m_message.empty()){ + embeds.emplace_back(std::pair("Message:", m_message)); + } + + if (m_send_error_report == ErrorReport::SEND_ERROR_REPORT){ + report_error( + &env.logger(), + env.program_info(), + name(), + embeds, + screenshot(), + m_console + ); + } + + send_program_notification( + env, notification, + COLOR_RED, + name(), + std::move(embeds), "", + screenshot() + ); +} + diff --git a/SerialPrograms/Source/CommonFramework/Exceptions/ScreenshotException.h b/SerialPrograms/Source/CommonFramework/Exceptions/ScreenshotException.h index 37f7697e8..5d2864eac 100644 --- a/SerialPrograms/Source/CommonFramework/Exceptions/ScreenshotException.h +++ b/SerialPrograms/Source/CommonFramework/Exceptions/ScreenshotException.h @@ -34,19 +34,38 @@ class ScreenshotException : public Exception{ explicit ScreenshotException(ErrorReport error_report, std::string message); explicit ScreenshotException(ErrorReport error_report, std::string message, std::shared_ptr screenshot); explicit ScreenshotException(ErrorReport error_report, ConsoleHandle& console, std::string message, bool take_screenshot); + explicit ScreenshotException( + ErrorReport error_report, + std::string message, + ConsoleHandle& console + ); + explicit ScreenshotException( + ErrorReport error_report, + std::string message, + ConsoleHandle* console, + ImageRGB32 screenshot + ); + explicit ScreenshotException( + ErrorReport error_report, + std::string message, + ConsoleHandle* console, + std::shared_ptr screenshot + ); + + void add_console_if_needed(ConsoleHandle& console); - void attach_screenshot(std::shared_ptr screenshot); public: // virtual const char* name() const override{ return "ScreenshotException"; } virtual std::string message() const override{ return m_message; } ImageViewRGB32 screenshot() const; - virtual void send_notification(ProgramEnvironment& env, EventNotificationOption& notification) const = 0; + virtual void send_notification(ProgramEnvironment& env, EventNotificationOption& notification) const; public: ErrorReport m_send_error_report; std::string m_message; + ConsoleHandle* m_console = nullptr; std::shared_ptr m_screenshot; }; diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp index 5062a9c79..f095d6adf 100644 --- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp +++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp @@ -286,7 +286,7 @@ GlobalSettings::GlobalSettings() PA_ADD_OPTION(ENABLE_FRAME_SCREENSHOTS); #endif #if QT_VERSION_MAJOR >= 6 - if (PreloadSettings::instance().DEVELOPER_MODE){ // REMOVE + if (PreloadSettings::instance().DEVELOPER_MODE){ PA_ADD_OPTION(STREAM_HISTORY); } #endif diff --git a/SerialPrograms/Source/CommonFramework/Globals.cpp b/SerialPrograms/Source/CommonFramework/Globals.cpp index 588f9caf9..ddcb62d55 100644 --- a/SerialPrograms/Source/CommonFramework/Globals.cpp +++ b/SerialPrograms/Source/CommonFramework/Globals.cpp @@ -25,7 +25,7 @@ namespace PokemonAutomation{ const bool IS_BETA_VERSION = true; const int PROGRAM_VERSION_MAJOR = 0; const int PROGRAM_VERSION_MINOR = 50; -const int PROGRAM_VERSION_PATCH = 8; +const int PROGRAM_VERSION_PATCH = 9; const std::string PROGRAM_VERSION_BASE = "v" + std::to_string(PROGRAM_VERSION_MAJOR) + diff --git a/SerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.cpp b/SerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.cpp index 288321137..fa99a2c0f 100644 --- a/SerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.cpp +++ b/SerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.cpp @@ -66,23 +66,29 @@ class HistorySaverThread : public QThread{ , m_tracker(tracker) , m_filename(filename) { - start(); } ~HistorySaverThread(){ quit(); wait(); } + bool save(){ + start(); + quit(); + wait(); + return m_success; + } virtual void run() override{ - m_tracker.save(m_logger, m_filename); + m_success = m_tracker.save(m_logger, m_filename); } private: Logger& m_logger; const StreamHistoryTracker& m_tracker; const std::string& m_filename; + bool m_success = false; }; -void StreamHistorySession::save(const std::string& filename) const{ +bool StreamHistorySession::save(const std::string& filename) const{ const Data& data = *m_data; // Get an owning reference to the current tracker. @@ -93,13 +99,14 @@ void StreamHistorySession::save(const std::string& filename) const{ WriteSpinLock lg(data.m_lock); if (!data.m_current){ data.m_logger.log("Cannot save stream history: Stream history is not enabled.", COLOR_RED); - return; + return false; } tracker = data.m_current; } // tracker->save(m_logger, filename); HistorySaverThread saver(data.m_logger, *tracker, filename); + return saver.save(); } void StreamHistorySession::on_samples(const float* samples, size_t frames){ Data& data = *m_data; diff --git a/SerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.h b/SerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.h index 5a2961a8c..dfd9855ee 100644 --- a/SerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.h +++ b/SerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.h @@ -26,7 +26,7 @@ class StreamHistorySession public: StreamHistorySession(Logger& logger); void start(AudioChannelFormat format); - void save(const std::string& filename) const; + bool save(const std::string& filename) const; public: virtual void on_samples(const float* data, size_t frames) override; diff --git a/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_Null.h b/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_Null.h index b33ee60ec..45b160baf 100644 --- a/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_Null.h +++ b/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_Null.h @@ -25,8 +25,9 @@ class StreamHistoryTracker{ ){} void set_window(std::chrono::seconds window){} - void save(Logger& logger, const std::string& filename) const{ + bool save(Logger& logger, const std::string& filename) const{ logger.log("Cannot save stream history: Not implemented.", COLOR_RED); + return false; } public: diff --git a/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_RecordOnTheFly.h b/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_RecordOnTheFly.h index e55e0ce4c..f14d17e99 100644 --- a/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_RecordOnTheFly.h +++ b/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_RecordOnTheFly.h @@ -10,20 +10,54 @@ #define PokemonAutomation_StreamHistoryTracker_RecordOnTheFly_H #include +#include #include #include +//#include #include #include #include #include #include +#include "Common/Cpp/LifetimeSanitizer.h" +#include "Common/Cpp/PrettyPrint.h" #include "Common/Cpp/AbstractLogger.h" +#include "Common/Cpp/Concurrency/SpinPause.h" #include "Common/Cpp/Concurrency/SpinLock.h" #include "CommonFramework/VideoPipeline/Backends/VideoFrameQt.h" +#include +using std::cout; +using std::endl; + namespace PokemonAutomation{ +class RollingStream : public QIODevice{ +public: + ~RollingStream(){ + waitForBytesWritten(-1); +// cout << "~RollingStream()" << endl; // REMOVE + } + RollingStream(){ + setOpenMode(QIODeviceBase::WriteOnly); + } + virtual qint64 readData(char* data, qint64 maxlen){ return 0; } + virtual qint64 writeData(const char* data, qint64 len){ + m_sanitizer.check_usage(); + m_bytes += len; + cout << "total = " << m_bytes << ", current = " << len << endl; + return len; + } + +private: + uint64_t m_bytes = 0; + + LifetimeSanitizer m_sanitizer; +}; + + + class StreamHistoryTracker{ public: ~StreamHistoryTracker(); @@ -34,7 +68,7 @@ class StreamHistoryTracker{ ); void set_window(std::chrono::seconds window); - void save(Logger& logger, const std::string& filename) const; + bool save(Logger& logger, const std::string& filename) const; public: // void push_frame(QVideoFrame frame); @@ -57,16 +91,27 @@ class StreamHistoryTracker{ // mutable std::mutex m_lock; // std::condition_variable m_cv; + RollingStream m_stream; + QAudioFormat m_audio_format; QAudioBufferInput m_audio_input; QVideoFrameInput m_video_input; QMediaCaptureSession m_session; + QMediaRecorder m_recorder; }; StreamHistoryTracker::~StreamHistoryTracker(){ m_recorder.stop(); + while (m_recorder.recorderState() != QMediaRecorder::StoppedState){ +// cout << "StreamHistoryTracker: process" << endl; + try{ + QCoreApplication::processEvents(); + pause(); + }catch (...){} + } +// cout << "~StreamHistoryTracker()" << endl; // REMOVE } StreamHistoryTracker::StreamHistoryTracker( size_t audio_samples_per_frame, @@ -90,10 +135,23 @@ StreamHistoryTracker::StreamHistoryTracker( m_recorder.setMediaFormat(QMediaFormat::MPEG4); m_recorder.setQuality(QMediaRecorder::HighQuality); - QFileInfo file(QString::fromStdString("video.mp4")); +#if 0 + m_recorder.connect( + &m_recorder, &QMediaRecorder::recorderStateChanged, + &m_recorder, [](QMediaRecorder::RecorderState state){ + + } + ); +#endif + + QFileInfo file(QString::fromStdString("capture-" + now_to_filestring() + ".mp4")); +#if 1 m_recorder.setOutputLocation( QUrl::fromLocalFile(file.absoluteFilePath()) ); +#else + m_recorder.setOutputDevice(&m_stream); +#endif m_recorder.record(); } @@ -116,11 +174,12 @@ void StreamHistoryTracker::on_frame(std::shared_ptr frame){ m_video_input.sendVideoFrame(frame->frame); } -void StreamHistoryTracker::save(Logger& logger, const std::string& filename) const{ +bool StreamHistoryTracker::save(Logger& logger, const std::string& filename) const{ logger.log("Cannot save stream history: Not implemented.", COLOR_RED); // TODO + return false; } diff --git a/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_SaveFrames.h b/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_SaveFrames.h index 69e339f7d..364c43804 100644 --- a/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_SaveFrames.h +++ b/SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_SaveFrames.h @@ -24,9 +24,11 @@ #include "CommonFramework/VideoPipeline/Backends/VideoFrameQt.h" -//#include -//using std::cout; -//using std::endl; +// REMOVE +#include +using std::cout; +using std::endl; + namespace PokemonAutomation{ @@ -54,7 +56,7 @@ class StreamHistoryTracker{ ); void set_window(std::chrono::seconds window); - void save(Logger& logger, const std::string& filename) const; + bool save(Logger& logger, const std::string& filename) const; public: void on_samples(const float* data, size_t frames); @@ -161,7 +163,7 @@ void StreamHistoryTracker::clear_old(){ -void StreamHistoryTracker::save(Logger& logger, const std::string& filename) const{ +bool StreamHistoryTracker::save(Logger& logger, const std::string& filename) const{ logger.log("Saving stream history...", COLOR_BLUE); std::deque> audio; @@ -170,7 +172,7 @@ void StreamHistoryTracker::save(Logger& logger, const std::string& filename) con // Fast copy the current state of the stream. WriteSpinLock lg(m_lock); if (m_audio.empty() && m_frames.empty()){ - return; + return false; } audio = m_audio; frames = m_frames; @@ -213,8 +215,16 @@ void StreamHistoryTracker::save(Logger& logger, const std::string& filename) con session.setAudioBufferInput(&audio_input); session.setVideoFrameInput(&video_input); session.setRecorder(&recorder); +#if 1 recorder.setMediaFormat(QMediaFormat::MPEG4); - recorder.setQuality(QMediaRecorder::HighQuality); +#else + QMediaFormat video_format; + video_format.setAudioCodec(QMediaFormat::AudioCodec::AAC); +// video_format.setVideoCodec(QMediaFormat::VideoCodec::H264); + video_format.setFileFormat(QMediaFormat::MPEG4); + recorder.setMediaFormat(video_format); +#endif + recorder.setQuality(QMediaRecorder::NormalQuality); QFileInfo file(QString::fromStdString(filename)); recorder.setOutputLocation( @@ -225,6 +235,7 @@ void StreamHistoryTracker::save(Logger& logger, const std::string& filename) con WallClock last_change = current_time(); QAudioBuffer audio_buffer; + bool success = true; while (audio_buffer.isValid() || !frames.empty()){ #if 1 while (true){ @@ -276,6 +287,7 @@ void StreamHistoryTracker::save(Logger& logger, const std::string& filename) con if (current_time() - last_change > std::chrono::seconds(10)){ logger.log("Failed to record stream history: No progress made after 10 seconds.", COLOR_RED); + success = false; break; } @@ -284,10 +296,11 @@ void StreamHistoryTracker::save(Logger& logger, const std::string& filename) con recorder.stop(); logger.log("Done saving stream history...", COLOR_BLUE); + cout << recorder.duration() << endl; // REMOVE // }); - + return success; } diff --git a/SerialPrograms/Source/CommonFramework/Tools/ConsoleHandle.cpp b/SerialPrograms/Source/CommonFramework/Tools/ConsoleHandle.cpp index 81f727082..87185d2f2 100644 --- a/SerialPrograms/Source/CommonFramework/Tools/ConsoleHandle.cpp +++ b/SerialPrograms/Source/CommonFramework/Tools/ConsoleHandle.cpp @@ -48,8 +48,8 @@ ConsoleHandle::ConsoleHandle( m_overlay.add_stat(*m_thread_utilization); } -void ConsoleHandle::save_stream_history(const std::string& filename){ - m_history.save(filename); +bool ConsoleHandle::save_stream_history(const std::string& filename){ + return m_history.save(filename); } void ConsoleHandle::initialize_inference_threads(CancellableScope& scope, AsyncDispatcher& dispatcher){ diff --git a/SerialPrograms/Source/CommonFramework/Tools/ConsoleHandle.h b/SerialPrograms/Source/CommonFramework/Tools/ConsoleHandle.h index 235aefb25..f70757698 100644 --- a/SerialPrograms/Source/CommonFramework/Tools/ConsoleHandle.h +++ b/SerialPrograms/Source/CommonFramework/Tools/ConsoleHandle.h @@ -59,7 +59,7 @@ class ConsoleHandle{ VideoFeed& video(){ return m_video; } VideoOverlay& overlay(){ return m_overlay; } AudioFeed& audio(){ return m_audio; } - void save_stream_history(const std::string& filename); + bool save_stream_history(const std::string& filename); operator Logger&(){ return m_logger; } operator VideoFeed&(){ return m_video; } diff --git a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp index 5c2120bc5..f30016494 100644 --- a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp +++ b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp @@ -276,7 +276,7 @@ void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope& // SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED); - console.save_stream_history("video.mp4"); +// console.save_stream_history("video.mp4"); #if 0 VideoSnapshot image = feed.snapshot(); @@ -290,7 +290,7 @@ void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope& ); #endif -#if 0 +#if 1 VideoSnapshot image = feed.snapshot(); // ImageRGB32 image("screenshot-20241124-135028529403.png"); diff --git a/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_MultiSwitchProgramSession.cpp b/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_MultiSwitchProgramSession.cpp index 46b3219b0..9b15e5994 100644 --- a/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_MultiSwitchProgramSession.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_MultiSwitchProgramSession.cpp @@ -169,8 +169,13 @@ void MultiSwitchProgramSession::internal_run_program(){ logger().log("Program finished early!", COLOR_BLUE); e.send_notification(env, m_option.instance().NOTIFICATION_PROGRAM_FINISH); }catch (InvalidConnectionStateException&){ - }catch (OperationFailedException& e){ + }catch (ScreenshotException& e){ logger().log("Program stopped with an exception!", COLOR_RED); + + // If the exception doesn't already have console information, + // attach the 1st console here. + e.add_console_if_needed(env.consoles[0]); + std::string message = e.message(); if (message.empty()){ message = e.name(); diff --git a/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_SingleSwitchProgramSession.cpp b/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_SingleSwitchProgramSession.cpp index d3ed4e905..e810d80e5 100644 --- a/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_SingleSwitchProgramSession.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Framework/NintendoSwitch_SingleSwitchProgramSession.cpp @@ -127,8 +127,9 @@ void SingleSwitchProgramSession::internal_run_program(){ logger().log("Program finished early!", COLOR_BLUE); e.send_notification(env, m_option.instance().NOTIFICATION_PROGRAM_FINISH); }catch (InvalidConnectionStateException&){ - }catch (OperationFailedException& e){ + }catch (ScreenshotException& e){ logger().log("Program stopped with an exception!", COLOR_RED); + e.add_console_if_needed(env.console); std::string message = e.message(); if (message.empty()){ message = e.name(); diff --git a/SerialPrograms/Source/PokemonSV/Programs/ShinyHunting/PokemonSV_ShinyHunt-AreaZeroPlatform.cpp b/SerialPrograms/Source/PokemonSV/Programs/ShinyHunting/PokemonSV_ShinyHunt-AreaZeroPlatform.cpp index 437ed6c80..200cbdc23 100644 --- a/SerialPrograms/Source/PokemonSV/Programs/ShinyHunting/PokemonSV_ShinyHunt-AreaZeroPlatform.cpp +++ b/SerialPrograms/Source/PokemonSV/Programs/ShinyHunting/PokemonSV_ShinyHunt-AreaZeroPlatform.cpp @@ -447,8 +447,9 @@ void ShinyHuntAreaZeroPlatform::set_flags_and_run_state(SingleSwitchProgramEnvir m_consecutive_failures++; e.send_notification(*m_env, NOTIFICATION_ERROR_RECOVERABLE); if (m_consecutive_failures >= 3){ - throw FatalProgramException( - ErrorReport::SEND_ERROR_REPORT, console, + throw_and_log( + console, + ErrorReport::SEND_ERROR_REPORT, "Failed 3 times consecutively." ); } diff --git a/SerialPrograms/Source/PokemonSV/Programs/TeraRaids/PokemonSV_AutoHostTools.cpp b/SerialPrograms/Source/PokemonSV/Programs/TeraRaids/PokemonSV_AutoHostTools.cpp index 1e675f666..89c7d6641 100644 --- a/SerialPrograms/Source/PokemonSV/Programs/TeraRaids/PokemonSV_AutoHostTools.cpp +++ b/SerialPrograms/Source/PokemonSV/Programs/TeraRaids/PokemonSV_AutoHostTools.cpp @@ -72,8 +72,9 @@ void TeraFailTracker::on_raid_start(){ m_current_raid_error.store(false, std::memory_order_relaxed); if (m_consecutive_failures > 0 && !m_completed_one){ - throw FatalProgramException( - ErrorReport::SEND_ERROR_REPORT, m_env.logger(), + throw_and_log( + m_env.logger(), + ErrorReport::SEND_ERROR_REPORT, "Failed 1st raid attempt. Will not retry due to risk of ban." ); } @@ -81,8 +82,9 @@ void TeraFailTracker::on_raid_start(){ if (m_consecutive_failures >= fail_threshold){ uint16_t minutes = m_failure_pause_minutes; if (minutes == 0){ - throw FatalProgramException( - ErrorReport::SEND_ERROR_REPORT, m_env.logger(), + throw_and_log( + m_env.logger(), + ErrorReport::SEND_ERROR_REPORT, "Failed " + std::to_string(fail_threshold) + " raid(s) in the row. " "Stopping to prevent possible ban." ); @@ -173,13 +175,15 @@ void KillSwitchTracker::check_kill_switch(const std::string& kill_switch_url){ ); if (start_time < m_killswitch_time && now > m_killswitch_time){ if (m_killswitch_reason.empty()){ - throw FatalProgramException( - ErrorReport::NO_ERROR_REPORT, m_env.logger(), + throw_and_log( + m_env.logger(), + ErrorReport::NO_ERROR_REPORT, "Stopped by remote kill switch. No reason specified." ); }else{ - throw FatalProgramException( - ErrorReport::NO_ERROR_REPORT, m_env.logger(), + throw_and_log( + m_env.logger(), + ErrorReport::NO_ERROR_REPORT, "Stopped by remote kill switch. Reason: " + m_killswitch_reason ); } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_CaughtScreen.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_CaughtScreen.cpp index e6993dcee..1225b11e1 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_CaughtScreen.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_CaughtScreen.cpp @@ -170,10 +170,11 @@ StateMachineAction run_caught_screen( synchronize_caught_screen(console, context, state_tracker); StateMachineAction state = mash_A_to_entrance(runtime, console, context, entrance); if (state == StateMachineAction::RESET_RECOVER){ - throw FatalProgramException( - ErrorReport::SEND_ERROR_REPORT, + throw_and_log( console, - "Unable to take " + Pokemon::STRING_POKEMON + ". Did you forget to disable nicknames?" + ErrorReport::SEND_ERROR_REPORT, + "Unable to take " + Pokemon::STRING_POKEMON + ". Did you forget to disable nicknames?", + console ); } return state;