Skip to content

Commit ce4e535

Browse files
authored
Merge pull request #95 from Legacy-LuaSTG-Engine/feature/redirect-stdout
[logging] Redirect stdout
2 parents a52b2aa + 552ce30 commit ce4e535

File tree

8 files changed

+229
-177
lines changed

8 files changed

+229
-177
lines changed

LuaSTG/LuaSTG/Custom/resource.rc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ END
5050
//
5151

5252
VS_VERSION_INFO VERSIONINFO
53-
FILEVERSION 0,21,117,0
54-
PRODUCTVERSION 0,21,117,0
53+
FILEVERSION 0,21,119,0
54+
PRODUCTVERSION 0,21,119,0
5555
FILEFLAGSMASK 0x3fL
5656
#ifdef _DEBUG
5757
FILEFLAGS 0x1L
@@ -68,12 +68,12 @@ BEGIN
6868
BEGIN
6969
VALUE "CompanyName", "璀境石"
7070
VALUE "FileDescription", "LuaSTG Sub"
71-
VALUE "FileVersion", "0.21.117.0"
71+
VALUE "FileVersion", "0.21.119.0"
7272
VALUE "InternalName", "LuaSTG Sub"
73-
VALUE "LegalCopyright", "Copyright 2020-2025 璀境石"
73+
VALUE "LegalCopyright", "Copyright 2020-2026 璀境石"
7474
VALUE "OriginalFilename", "LuaSTGSub.exe"
7575
VALUE "ProductName", "LuaSTG Sub"
76-
VALUE "ProductVersion", "0.21.117.0"
76+
VALUE "ProductVersion", "0.21.119.0"
7777
END
7878
END
7979
BLOCK "VarFileInfo"

LuaSTG/LuaSTG/Debugger/Logger.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "Debugger/Logger.hpp"
33
#include "spdlog/spdlog.h"
44
#include "spdlog/sinks/basic_file_sink.h"
5+
#include "spdlog/sinks/stdout_sinks.h"
56
#include "spdlog/sinks/wincolor_sink.h"
67
#include "spdlog/sinks/msvc_sink.h"
78
#include "Platform/HResultChecker.hpp"
@@ -11,6 +12,7 @@
1112

1213
namespace {
1314
std::filesystem::path rolling_file_root;
15+
bool redirectStdOut();
1416
bool openWin32Console();
1517
void closeWin32Console();
1618
std::string generateRollingFileName() {
@@ -95,6 +97,13 @@ namespace luastg {
9597
}
9698
#endif
9799

100+
if (auto const& standard_output = config.getStandardOutput(); standard_output.isEnable() && redirectStdOut()) {
101+
auto sink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
102+
sink->set_pattern("[%Y-%m-%d %H:%M:%S] [%L] %v");
103+
sink->set_level(mapLevel(standard_output.getThreshold()));
104+
sinks.emplace_back(sink);
105+
}
106+
98107
if (auto const& logging_file = config.getFile(); logging_file.isEnable()) {
99108
std::filesystem::path file_path;
100109
if (logging_file.hasPath()) {
@@ -177,9 +186,40 @@ namespace luastg {
177186
}
178187
};
179188

189+
#include <fcntl.h>
180190
#include "Platform/CleanWindows.hpp"
181191

182192
namespace {
193+
bool redirectStdOut() {
194+
const auto handle = GetStdHandle(STD_OUTPUT_HANDLE);
195+
if (handle == nullptr || handle == INVALID_HANDLE_VALUE) {
196+
#ifndef NDEBUG
197+
OutputDebugStringA("GetStdHandle failed\n");
198+
#endif
199+
return false;
200+
}
201+
202+
const auto fd = _open_osfhandle(reinterpret_cast<intptr_t>(handle), _O_WRONLY | _O_BINARY);
203+
if (fd == -1) {
204+
#ifndef NDEBUG
205+
OutputDebugStringA("_open_osfhandle failed\n");
206+
#endif
207+
return false;
208+
}
209+
210+
if (_dup2(fd, _fileno(stdout)) == -1) {
211+
#ifndef NDEBUG
212+
OutputDebugStringA("_dup2 failed\n");
213+
#endif
214+
_close(fd);
215+
return false;
216+
}
217+
218+
_close(fd);
219+
setvbuf(stdout, nullptr, _IONBF, 0);
220+
221+
return true;
222+
}
183223
bool g_alloc_console{ false };
184224
bool openWin32Console() {
185225
auto const& logging_console = core::ConfigurationLoader::getInstance().getLogging().getConsole();

LuaSTG/LuaSTG/LConfig.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
#define LUASTG_NAME "LuaSTG"
44
#define LUASTG_BRANCH "Sub"
5-
#define LUASTG_VERSION_NAME "v0.21.117"
5+
#define LUASTG_VERSION_NAME "v0.21.119"
66
#define LUASTG_VERSION_MAJOR 0
77
#define LUASTG_VERSION_MINOR 21
8-
#define LUASTG_VERSION_PATCH 117
8+
#define LUASTG_VERSION_PATCH 119
99

1010
#define LUASTG_INFO LUASTG_NAME " " LUASTG_BRANCH " " LUASTG_VERSION_NAME
1111

LuaSTG/LuaSTG/LuaSTG.manifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
3-
<assemblyIdentity type="win32" name="ChuiKingShek.LuaSTG.Sub" version="0.21.117.0"/>
3+
<assemblyIdentity type="win32" name="ChuiKingShek.LuaSTG.Sub" version="0.21.119.0"/>
44
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
55
<security>
66
<requestedPrivileges>

engine/configuration/core/Configuration.cpp

Lines changed: 168 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "core/Configuration.hpp"
2+
#include "core/CommandLineArguments.hpp"
23
#include <iostream>
34
#include <fstream>
45
#include <filesystem>
@@ -44,18 +45,18 @@
4445

4546
using namespace std::string_view_literals;
4647

47-
namespace core {
48-
static std::u8string_view to_u8string_view(std::string_view const& sv) {
48+
namespace {
49+
std::u8string_view to_u8string_view(std::string_view const& sv) {
4950
static_assert(CHAR_BIT == 8 && sizeof(char) == 1 && sizeof(char) == sizeof(char8_t));
50-
return { reinterpret_cast<char8_t const*>(sv.data()), sv.size() };
51+
return { reinterpret_cast<char8_t const*>(sv.data()), sv.length() };
5152
}
5253

53-
static std::string to_string(std::u8string const& s) {
54+
std::string to_string(std::u8string const& s) {
5455
static_assert(CHAR_BIT == 8 && sizeof(char) == 1 && sizeof(char) == sizeof(char8_t));
5556
return { reinterpret_cast<char const*>(s.c_str()), s.length() };
5657
}
5758

58-
static bool is_uuid(std::string_view const& uuid) {
59+
bool is_uuid(std::string_view const& uuid) {
5960
#define is_uuid_char(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z'))
6061
#define is_not_uuid_char(c) (!is_uuid_char(c))
6162
constexpr std::string_view format36{ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"sv };
@@ -89,7 +90,7 @@ namespace core {
8990
#undef is_not_uuid_char
9091
}
9192

92-
static bool readTextFile(std::string_view const& path, std::string& str) {
93+
bool readTextFile(std::string_view const& path, std::string& str) {
9394
str.clear();
9495
std::error_code ec;
9596
std::filesystem::path fs_path(to_u8string_view(path));
@@ -114,7 +115,7 @@ namespace core {
114115
return true;
115116
}
116117

117-
static std::string_view getTypeName(nlohmann::json const& json) {
118+
std::string_view getTypeName(nlohmann::json const& json) {
118119
if (json.is_object()) {
119120
return "object"sv;
120121
}
@@ -262,6 +263,21 @@ namespace core {
262263
loader.logging.console.setPreserve(preserve.get<bool>());
263264
}
264265
}
266+
if (logging.contains("standard_output"sv)) {
267+
auto const& standard_output = logging.at("standard_output"sv);
268+
assert_type_is_object(standard_output, "/logging/standard_output"sv);
269+
if (standard_output.contains("enable"sv)) {
270+
auto const& enable = standard_output.at("enable"sv);
271+
assert_type_is_boolean(enable, "/logging/standard_output/enable"sv);
272+
loader.logging.standard_output.setEnable(enable.get<bool>());
273+
}
274+
if (standard_output.contains("threshold"sv)) {
275+
auto const& threshold = standard_output.at("threshold"sv);
276+
assert_type_is_string(threshold, "/logging/standard_output/threshold"sv);
277+
get_level("/logging/standard_output/threshold");
278+
loader.logging.standard_output.setThreshold(level);
279+
}
280+
}
265281
if (logging.contains("file"sv)) {
266282
auto const& file = logging.at("file"sv);
267283
assert_type_is_object(file, "/logging/file"sv);
@@ -630,3 +646,148 @@ namespace core {
630646
return instance;
631647
}
632648
}
649+
650+
namespace {
651+
std::optional<bool> to_boolean(std::string_view const& s) {
652+
if (s == "true"sv) {
653+
return { true };
654+
}
655+
else if (s == "false"sv) {
656+
return { false };
657+
}
658+
else {
659+
return std::nullopt;
660+
}
661+
}
662+
663+
template<typename T>
664+
std::optional<T> to_unsigned_integer(std::string_view const& s) {
665+
static_assert(std::is_unsigned_v<T>);
666+
T value{};
667+
auto const result = std::from_chars(s.data(), s.data() + s.size(), value);
668+
if (result.ec == std::errc{}) {
669+
return { value };
670+
}
671+
else {
672+
return std::nullopt;
673+
}
674+
}
675+
676+
template<typename T>
677+
std::optional<T> to_number(const std::string_view& s) {
678+
static_assert(std::is_same_v<float, T> || std::is_same_v<double, T>);
679+
const auto begin = s.data();
680+
const auto end = s.data() + s.size();
681+
T value{};
682+
const auto result = std::from_chars(begin, end, value);
683+
if (result.ec != std::errc{} || result.ptr != end) {
684+
return std::nullopt;
685+
}
686+
return value;
687+
}
688+
689+
enum class OptionType {
690+
boolean,
691+
number,
692+
string,
693+
};
694+
695+
using nlohmann::operator ""_json_pointer;
696+
697+
struct OptionMetadata {
698+
OptionType type;
699+
std::string_view prefix;
700+
nlohmann::json_pointer<std::string> path;
701+
};
702+
703+
const OptionMetadata meta[]{
704+
// debug
705+
{ .type = OptionType::boolean, .prefix = "--debug.track_window_focus="sv, .path = "/debug/track_window_focus"_json_pointer },
706+
// application
707+
{ .type = OptionType::string , .prefix = "--application.uuid="sv , .path = "/application/uuid"_json_pointer },
708+
{ .type = OptionType::boolean, .prefix = "--application.single_instance="sv, .path = "/application/single_instance"_json_pointer },
709+
// logging
710+
{ .type = OptionType::boolean, .prefix = "--logging.debugger.enable="sv , .path = "/logging/debugger/enable"_json_pointer },
711+
{ .type = OptionType::string , .prefix = "--logging.debugger.threshold="sv , .path = "/logging/debugger/threshold"_json_pointer },
712+
{ .type = OptionType::boolean, .prefix = "--logging.console.enable="sv , .path = "/logging/console/enable"_json_pointer },
713+
{ .type = OptionType::string , .prefix = "--logging.console.threshold="sv , .path = "/logging/console/threshold"_json_pointer },
714+
{ .type = OptionType::boolean, .prefix = "--logging.console.preserve="sv , .path = "/logging/console/preserve"_json_pointer },
715+
{ .type = OptionType::boolean, .prefix = "--logging.standard_output.enable="sv , .path = "/logging/standard_output/enable"_json_pointer },
716+
{ .type = OptionType::string , .prefix = "--logging.standard_output.threshold="sv, .path = "/logging/standard_output/threshold"_json_pointer },
717+
{ .type = OptionType::boolean, .prefix = "--logging.file.enable="sv , .path = "/logging/file/enable"_json_pointer },
718+
{ .type = OptionType::string , .prefix = "--logging.file.threshold="sv , .path = "/logging/file/threshold"_json_pointer },
719+
{ .type = OptionType::string , .prefix = "--logging.file.path="sv , .path = "/logging/file/path"_json_pointer },
720+
{ .type = OptionType::boolean, .prefix = "--logging.rolling_file.enable="sv , .path = "/logging/rolling_file/enable"_json_pointer },
721+
{ .type = OptionType::string , .prefix = "--logging.rolling_file.threshold="sv , .path = "/logging/rolling_file/threshold"_json_pointer },
722+
{ .type = OptionType::string , .prefix = "--logging.rolling_file.path="sv , .path = "/logging/rolling_file/path"_json_pointer },
723+
{ .type = OptionType::number , .prefix = "--logging.rolling_file.max_history="sv , .path = "/logging/rolling_file/max_history"_json_pointer },
724+
// timing
725+
{ .type = OptionType::number , .prefix = "--timing.frame_rate="sv, .path = ""_json_pointer },
726+
// window
727+
{ .type = OptionType::string , .prefix = "--window.title="sv , .path = "/window/title"_json_pointer },
728+
{ .type = OptionType::boolean, .prefix = "--window.cursor_visible="sv , .path = "/window/cursor_visible"_json_pointer },
729+
{ .type = OptionType::boolean, .prefix = "--window.allow_window_corner="sv , .path = "/window/allow_window_corner"_json_pointer },
730+
{ .type = OptionType::boolean, .prefix = "--window.allow_title_bar_auto_hide="sv, .path = "/window/allow_title_bar_auto_hide"_json_pointer },
731+
// graphics_system
732+
{ .type = OptionType::string , .prefix = "--graphics_system.preferred_device_name="sv , .path = "/graphics_system/preferred_device_name"_json_pointer },
733+
{ .type = OptionType::number , .prefix = "--graphics_system.width="sv , .path = "/graphics_system/width"_json_pointer },
734+
{ .type = OptionType::number , .prefix = "--graphics_system.height="sv , .path = "/graphics_system/height"_json_pointer },
735+
{ .type = OptionType::boolean, .prefix = "--graphics_system.fullscreen="sv , .path = "/graphics_system/fullscreen"_json_pointer },
736+
{ .type = OptionType::boolean, .prefix = "--graphics_system.vsync="sv , .path = "/graphics_system/vsync"_json_pointer },
737+
{ .type = OptionType::boolean, .prefix = "--graphics_system.allow_software_device="sv , .path = "/graphics_system/allow_software_device"_json_pointer },
738+
{ .type = OptionType::boolean, .prefix = "--graphics_system.allow_exclusive_fullscreen="sv, .path = "/graphics_system/allow_exclusive_fullscreen"_json_pointer },
739+
{ .type = OptionType::boolean, .prefix = "--graphics_system.allow_modern_swap_chain="sv , .path = "/graphics_system/allow_modern_swap_chain"_json_pointer },
740+
{ .type = OptionType::boolean, .prefix = "--graphics_system.allow_direct_composition="sv , .path = "/graphics_system/allow_direct_composition"_json_pointer },
741+
// audio_system
742+
{ .type = OptionType::string , .prefix = "--audio_system.preferred_endpoint_name="sv, .path = "/audio_system/preferred_endpoint_name"_json_pointer },
743+
{ .type = OptionType::number , .prefix = "--audio_system.sound_effect_volume="sv , .path = "/audio_system/sound_effect_volume"_json_pointer },
744+
{ .type = OptionType::number , .prefix = "--audio_system.music_volume="sv , .path = "/audio_system/music_volume"_json_pointer },
745+
};
746+
}
747+
748+
namespace core {
749+
bool ConfigurationLoader::loadFromCommandLineArguments() {
750+
const auto args{ CommandLineArguments::copy() };
751+
const auto write_arg_error = [this](const std::string_view& raw_arg) -> void {
752+
messages.emplace_back(std::format("invalid command line argument '{}'", raw_arg));
753+
};
754+
int error_counter{};
755+
nlohmann::json json;
756+
for (size_t i = 0; i < args.size(); i += 1) {
757+
const std::string_view arg(args[i]);
758+
for (const auto& t : meta) {
759+
if (arg.starts_with(t.prefix)) {
760+
const std::string_view value{ arg.substr(t.prefix.length()) };
761+
switch (t.type) {
762+
case OptionType::boolean:
763+
if (const auto result = to_boolean(value); result) {
764+
json[t.path] = result.value();
765+
}
766+
else {
767+
write_arg_error(arg);
768+
error_counter += 1;
769+
}
770+
break; // switch (t.type)
771+
case OptionType::number:
772+
if (const auto result = to_number<double>(value); result) {
773+
json[t.path] = result.value();
774+
}
775+
else {
776+
write_arg_error(arg);
777+
error_counter += 1;
778+
}
779+
break; // switch (t.type)
780+
case OptionType::string:
781+
json[t.path] = value;
782+
break; // switch (t.type)
783+
}
784+
break; // for (const auto& t : meta)
785+
}
786+
}
787+
}
788+
if (error_counter > 0) {
789+
return false;
790+
}
791+
return ConfigurationLoaderContext::merge(*this, nullptr, json);
792+
}
793+
}

engine/configuration/core/Configuration.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ namespace core {
6464
private:
6565
bool preserve{ false };
6666
};
67+
class StandardOutput : public Base {
68+
};
6769
class File : public Base {
6870
public:
6971
GetterSetterString(File, path, Path);
@@ -79,11 +81,13 @@ namespace core {
7981
public:
8082
inline Debugger const& getDebugger() const noexcept { return debugger; }
8183
inline Console const& getConsole() const noexcept { return console; }
84+
inline StandardOutput const& getStandardOutput() const noexcept { return standard_output; }
8285
inline File const& getFile() const noexcept { return file; }
8386
inline RollingFile const& getRollingFile() const noexcept { return rolling_file; }
8487
private:
8588
Debugger debugger;
8689
Console console;
90+
StandardOutput standard_output;
8791
File file;
8892
RollingFile rolling_file;
8993
};

0 commit comments

Comments
 (0)