experiment with http stuff
seems to work!
This commit is contained in:
parent
0379b729eb
commit
3c585b3a04
21 changed files with 623 additions and 49 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -6,3 +6,6 @@
|
||||||
# on your own machine, please.
|
# on your own machine, please.
|
||||||
/speech2/build
|
/speech2/build
|
||||||
/speech2/build-debug
|
/speech2/build-debug
|
||||||
|
|
||||||
|
# cmake tools is viciously unaware of subdirectories
|
||||||
|
/build
|
||||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -196,3 +196,6 @@
|
||||||
[submodule "speech2/third_party/boost/asio"]
|
[submodule "speech2/third_party/boost/asio"]
|
||||||
path = speech2/third_party/boost/asio
|
path = speech2/third_party/boost/asio
|
||||||
url = https://github.com/boostorg/asio.git
|
url = https://github.com/boostorg/asio.git
|
||||||
|
[submodule "speech2/third_party/boost/beast"]
|
||||||
|
path = speech2/third_party/boost/beast
|
||||||
|
url = https://github.com/boostorg/beast.git
|
||||||
|
|
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"cmake.sourceDirectory": "${workspaceFolder}/speech2",
|
||||||
|
"cmake.configureArgs": [
|
||||||
|
"--toolchain cmake/clangcl-winxp.cmake"
|
||||||
|
],
|
||||||
|
"cmake.configureEnvironment": {
|
||||||
|
"VCDIR": "${env:HOME}/vs2022"
|
||||||
|
},
|
||||||
|
"cmake.ignoreCMakeListsMissing": true,
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
add_subdirectory(base)
|
||||||
|
add_subdirectory(impl)
|
||||||
add_subdirectory(sapi4)
|
add_subdirectory(sapi4)
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,25 +17,23 @@ target_compile_definitions(sapiserver PRIVATE
|
||||||
# Need to force this on, since I think clang's msvc compatibility
|
# Need to force this on, since I think clang's msvc compatibility
|
||||||
# is deciding to set a wrong __cplusplus (like MSVC, so it's not *exactly* clang's fault).
|
# is deciding to set a wrong __cplusplus (like MSVC, so it's not *exactly* clang's fault).
|
||||||
# The best way to fix it would probably involve using clang-cl frontend and passing the option (I think.)
|
# The best way to fix it would probably involve using clang-cl frontend and passing the option (I think.)
|
||||||
|
# (nevermind, it's just broken.)
|
||||||
-DBOOST_ASIO_HAS_STD_INVOKE_RESULT=1
|
-DBOOST_ASIO_HAS_STD_INVOKE_RESULT=1
|
||||||
|
|
||||||
# Disable the "helpful" auto-link Boost.Config tries to do. CMake already has a functional
|
# Disable the "helpful" auto-link Boost.Config tries to do. CMake already has a functional
|
||||||
# dependency graph, so we don't need it.
|
# dependency graph based on our input, so we don't need it.
|
||||||
-DBOOST_ALL_NO_LIB=1
|
-DBOOST_ALL_NO_LIB=1
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(sapiserver PRIVATE
|
target_link_libraries(sapiserver PRIVATE
|
||||||
# runtime libs
|
#libc++
|
||||||
libc++
|
|
||||||
|
|
||||||
# subprojects
|
# subprojects
|
||||||
speech2_sapi4
|
speech2::base
|
||||||
|
speech2::impl
|
||||||
|
speech2::api_sapi4
|
||||||
|
|
||||||
# SDK libraries
|
# SDK libraries
|
||||||
uuid.lib
|
uuid.lib
|
||||||
ole32.lib
|
ole32.lib
|
||||||
|
|
||||||
Boost::asio
|
|
||||||
Boost::coroutine
|
|
||||||
Boost::context
|
|
||||||
)
|
)
|
||||||
|
|
14
speech2/src/base/CMakeLists.txt
Normal file
14
speech2/src/base/CMakeLists.txt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
add_library(speech2_base STATIC
|
||||||
|
|
||||||
|
Thread.cpp
|
||||||
|
|
||||||
|
# Logging system
|
||||||
|
Logger.cpp
|
||||||
|
Logger_priv.cpp
|
||||||
|
StdoutSink.cpp
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
speech2_target(speech2_base)
|
||||||
|
|
||||||
|
add_library(speech2::base ALIAS speech2_base)
|
41
speech2/src/base/Logger.cpp
Normal file
41
speech2/src/base/Logger.cpp
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#include "Logger.hpp"
|
||||||
|
|
||||||
|
#include <base/Logger_priv.hpp>
|
||||||
|
#include <base/Thread.hpp>
|
||||||
|
|
||||||
|
namespace base {
|
||||||
|
|
||||||
|
inline auto& GlobalState() {
|
||||||
|
return logger_impl::LoggerGlobalState::The();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggerAttachSink(LoggerSink& sink) {
|
||||||
|
GlobalState().AttachSink(sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageSeverity GetLogLevel() {
|
||||||
|
return GlobalState().GetLogLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetLogLevel(MessageSeverity newLevel) {
|
||||||
|
GlobalState().SetLogLevel(newLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger& Logger::Get(std::string_view key) {
|
||||||
|
return logger_impl::GetOrRegister(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::Logger(ChannelId id)
|
||||||
|
: channelId(id) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::VOut(MessageSeverity severity, std::string_view format, std::format_args args) {
|
||||||
|
logger_impl::MessageData data {
|
||||||
|
.time = std::chrono::system_clock::now(), .severity = severity, .channelId = channelId, .message = std::vformat(format, args)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Push data into logger thread.
|
||||||
|
logger_impl::PushMessage(std::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace common
|
75
speech2/src/base/Logger.hpp
Normal file
75
speech2/src/base/Logger.hpp
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
#include <base/Types.hpp>
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace base {
|
||||||
|
|
||||||
|
/// A logger sink. Outputs messages to some device (a TTY), a file,
|
||||||
|
/// anything. A interface for the logger to spit stuff out.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// Sinks *do not* run on the main application thread. Instead, they run on a
|
||||||
|
/// single internal thread shared with the logging system.
|
||||||
|
/// Sinks should *not* block for large periods of time.
|
||||||
|
struct LoggerSink {
|
||||||
|
virtual void OutputMessage(std::string_view message) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class MessageSeverity { Debug, Info, Warning, Error, Fatal };
|
||||||
|
|
||||||
|
/// A channel ID. `enum class`es are used to avoid confusion with a normal u32,
|
||||||
|
/// and to also add addional interface type safety.
|
||||||
|
/// These are opaque, and only exposed here because it would be annoying to move elsewhere.
|
||||||
|
enum class ChannelId : u32 {};
|
||||||
|
|
||||||
|
/// Attach a sink to all Support loggers; allowing it to output logger messages.
|
||||||
|
void LoggerAttachSink(LoggerSink& sink);
|
||||||
|
|
||||||
|
MessageSeverity GetLogLevel();
|
||||||
|
|
||||||
|
void SetLogLevel(MessageSeverity newLevel);
|
||||||
|
|
||||||
|
/// An (asynchronous) logger.
|
||||||
|
struct Logger {
|
||||||
|
/// Gets or creates a logger for the specified channel.
|
||||||
|
static Logger& Get(std::string_view channel);
|
||||||
|
|
||||||
|
explicit Logger(ChannelId channel);
|
||||||
|
|
||||||
|
Logger(const Logger&) = delete;
|
||||||
|
Logger(Logger&&) = delete;
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
inline void Debug(std::string_view fmt, Args&&... args) {
|
||||||
|
VOut(MessageSeverity::Debug, fmt, std::make_format_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
inline void Info(std::string_view fmt, Args&&... args) {
|
||||||
|
VOut(MessageSeverity::Info, fmt, std::make_format_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
inline void Warning(std::string_view fmt, Args&&... args) {
|
||||||
|
VOut(MessageSeverity::Warning, fmt, std::make_format_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
inline void Error(std::string_view fmt, Args&&... args) {
|
||||||
|
VOut(MessageSeverity::Error, fmt, std::make_format_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
inline void Fatal(std::string_view fmt, Args&&... args) {
|
||||||
|
VOut(MessageSeverity::Fatal, fmt, std::make_format_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void VOut(MessageSeverity severity, std::string_view format, std::format_args args);
|
||||||
|
|
||||||
|
ChannelId channelId;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace collabvm
|
170
speech2/src/base/Logger_priv.cpp
Normal file
170
speech2/src/base/Logger_priv.cpp
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
#include <base/Logger_priv.hpp>
|
||||||
|
#include <base/Thread.hpp>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <string_view>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace base::logger_impl {
|
||||||
|
|
||||||
|
static constexpr std::string_view SeverityToString(MessageSeverity sev) {
|
||||||
|
// This must match order of MessageSeverity.
|
||||||
|
const char* MessageSeverityStringTable[] = { "Debug", "Info", "Warn", "Error", "Fatal" };
|
||||||
|
return MessageSeverityStringTable[static_cast<std::size_t>(sev)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hash algorithm for channel IDs. In this case it's PJW-ELF.
|
||||||
|
/// I might switch to murmur or something if collisions are a problem,
|
||||||
|
/// but I don't think it's a problem.
|
||||||
|
ChannelId ChannelIDHash(const char* in) {
|
||||||
|
u32 hash = 0;
|
||||||
|
u32 high = 0;
|
||||||
|
|
||||||
|
while(*in) {
|
||||||
|
hash = (hash << 4) + *in++;
|
||||||
|
|
||||||
|
if((high = hash & 0xf0000000))
|
||||||
|
hash ^= high >> 23;
|
||||||
|
|
||||||
|
hash &= ~high;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<ChannelId>(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view ChannelToString(ChannelId id) {
|
||||||
|
auto& gs = LoggerGlobalState::The();
|
||||||
|
std::unique_lock lk(gs.loggerMapLock);
|
||||||
|
return gs.loggerMap[id].channelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// comparator for [std::priority_queue]
|
||||||
|
struct LogMessageComparator {
|
||||||
|
constexpr bool operator()(const MessageData& mdLeft, const MessageData& mdRight) { return mdLeft.time > mdRight.time; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoggerThreadData {
|
||||||
|
// Logger thread stuff
|
||||||
|
std::thread loggerThread;
|
||||||
|
std::mutex logQueueMutex;
|
||||||
|
std::condition_variable logQueueCv;
|
||||||
|
|
||||||
|
std::priority_queue<MessageData, std::deque<MessageData>, LogMessageComparator> logQueue;
|
||||||
|
|
||||||
|
// could be an atomic_bool
|
||||||
|
bool logThreadShutdown = false;
|
||||||
|
|
||||||
|
bool ShouldUnblock() {
|
||||||
|
// N.B: ALL calls of this hold the lock.
|
||||||
|
|
||||||
|
// Always unblock if the logger thread needs to be shut down.
|
||||||
|
if(logThreadShutdown)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return !logQueue.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PushMessage(MessageData&& md) {
|
||||||
|
{
|
||||||
|
std::unique_lock lk(logQueueMutex);
|
||||||
|
logQueue.push(std::move(md));
|
||||||
|
}
|
||||||
|
logQueueCv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This thread drives the logging system.
|
||||||
|
static void LoggerThread(LoggerThreadData* self) {
|
||||||
|
// Fancy thread names.
|
||||||
|
SetThreadName("AsyncLogger");
|
||||||
|
|
||||||
|
auto& state = LoggerGlobalState::The();
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
// Shutdown if requested.
|
||||||
|
if(self->logThreadShutdown)
|
||||||
|
break;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock lk(self->logQueueMutex);
|
||||||
|
if(self->logQueue.empty()) {
|
||||||
|
// Await for messages.
|
||||||
|
self->logQueueCv.wait(lk, [self]() { return self->ShouldUnblock(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock lk(self->logQueueMutex);
|
||||||
|
// Flush the logger queue until there are no more messages.
|
||||||
|
while(!self->logQueue.empty()) {
|
||||||
|
const auto& msg = self->logQueue.top();
|
||||||
|
state.OutputMessage(msg);
|
||||||
|
self->logQueue.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Unique<LoggerThreadData> threadData;
|
||||||
|
|
||||||
|
LoggerGlobalState& LoggerGlobalState::The() {
|
||||||
|
static LoggerGlobalState storage;
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerGlobalState::LoggerGlobalState() {
|
||||||
|
// Spawn the logger thread
|
||||||
|
threadData = std::make_unique<LoggerThreadData>();
|
||||||
|
threadData->loggerThread = std::thread(&LoggerThreadData::LoggerThread, threadData.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerGlobalState::~LoggerGlobalState() {
|
||||||
|
// Shut down the logger thread
|
||||||
|
threadData->logThreadShutdown = true;
|
||||||
|
threadData->logQueueCv.notify_all();
|
||||||
|
threadData->loggerThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggerGlobalState::AttachSink(LoggerSink& sink) {
|
||||||
|
sinks.push_back(&sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggerGlobalState::OutputMessage(const MessageData& data) {
|
||||||
|
// give up early if no sinks are attached
|
||||||
|
if(sinks.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(data.severity < logLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto formattedLoggerMessage = std::format("[{:%F %H:%M:%S}|{}|{}] {}", std::chrono::floor<std::chrono::milliseconds>(data.time),
|
||||||
|
SeverityToString(data.severity), ChannelToString(data.channelId), data.message);
|
||||||
|
|
||||||
|
for(auto sink : sinks)
|
||||||
|
sink->OutputMessage(formattedLoggerMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger& GetOrRegister(std::string_view component) {
|
||||||
|
auto& gs = LoggerGlobalState::The();
|
||||||
|
|
||||||
|
std::unique_lock lk(gs.loggerMapLock);
|
||||||
|
|
||||||
|
auto hash = ChannelIDHash(component.data());
|
||||||
|
|
||||||
|
if(!gs.loggerMap.contains(hash)) {
|
||||||
|
// Insert a new entry into the logger map.
|
||||||
|
gs.loggerMap.insert_or_assign(hash, BoltedLoggerData { component, std::make_unique<Logger>(hash) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return *gs.loggerMap[hash].logger.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PushMessage(MessageData&& md) {
|
||||||
|
if(threadData)
|
||||||
|
threadData->PushMessage(std::move(md));
|
||||||
|
}
|
||||||
|
} // namespace base::logger_impl
|
59
speech2/src/base/Logger_priv.hpp
Normal file
59
speech2/src/base/Logger_priv.hpp
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <base/Logger.hpp>
|
||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace base::logger_impl {
|
||||||
|
|
||||||
|
/// Message data. This is only used by logger sinks.
|
||||||
|
struct MessageData {
|
||||||
|
std::chrono::system_clock::time_point time;
|
||||||
|
MessageSeverity severity;
|
||||||
|
|
||||||
|
ChannelId channelId; // the channel ID.
|
||||||
|
|
||||||
|
std::string message; // DO NOT SET THIS, IT WILL BE OVERWRITTEN AND I WILL BE VERY SAD -lily
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoltedLoggerData {
|
||||||
|
std::string_view channelName;
|
||||||
|
Unique<Logger> logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Shared global state all loggers use.
|
||||||
|
struct LoggerGlobalState {
|
||||||
|
static LoggerGlobalState& The();
|
||||||
|
|
||||||
|
void AttachSink(LoggerSink& sink);
|
||||||
|
|
||||||
|
void OutputMessage(const MessageData& data);
|
||||||
|
|
||||||
|
/// Get the current log level.
|
||||||
|
MessageSeverity GetLogLevel() const { return logLevel; }
|
||||||
|
|
||||||
|
/// Set the current log level.
|
||||||
|
void SetLogLevel(MessageSeverity newLogLevel) { logLevel = newLogLevel; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
LoggerGlobalState();
|
||||||
|
~LoggerGlobalState();
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::vector<LoggerSink*> sinks;
|
||||||
|
MessageSeverity logLevel { MessageSeverity::Info };
|
||||||
|
|
||||||
|
std::unordered_map<ChannelId, BoltedLoggerData> loggerMap;
|
||||||
|
std::mutex loggerMapLock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Gets or registers a new logger. This routine is threadsafe, and can be called
|
||||||
|
/// on any thread, like (most) parts of the logging system.
|
||||||
|
Logger& GetOrRegister(std::string_view component);
|
||||||
|
|
||||||
|
/// Push a logger message into the queue.
|
||||||
|
void PushMessage(MessageData&& md);
|
||||||
|
|
||||||
|
} // namespace base::logger_impl
|
20
speech2/src/base/StdoutSink.cpp
Normal file
20
speech2/src/base/StdoutSink.cpp
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#include <base/StdoutSink.hpp>
|
||||||
|
|
||||||
|
#include "Logger.hpp"
|
||||||
|
|
||||||
|
namespace base {
|
||||||
|
StdoutLoggerSink& StdoutLoggerSink::The() {
|
||||||
|
static StdoutLoggerSink sink;
|
||||||
|
return sink;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StdoutLoggerSink::OutputMessage(std::string_view message) {
|
||||||
|
fputs(message.data(), stdout);
|
||||||
|
fputc('\n', stdout);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggerAttachStdout() {
|
||||||
|
LoggerAttachSink(StdoutLoggerSink::The());
|
||||||
|
}
|
||||||
|
} // namespace base
|
17
speech2/src/base/StdoutSink.hpp
Normal file
17
speech2/src/base/StdoutSink.hpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <base/Logger.hpp>
|
||||||
|
|
||||||
|
namespace base {
|
||||||
|
|
||||||
|
/// A logger sink implementation that prints to standard output.
|
||||||
|
struct StdoutLoggerSink : public LoggerSink {
|
||||||
|
static StdoutLoggerSink& The();
|
||||||
|
|
||||||
|
void OutputMessage(std::string_view message) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Attach the stdout logger sink to the global Lucore logger.
|
||||||
|
void LoggerAttachStdout();
|
||||||
|
|
||||||
|
} // namespace base
|
11
speech2/src/base/Thread.cpp
Normal file
11
speech2/src/base/Thread.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <base/Thread.hpp>
|
||||||
|
|
||||||
|
namespace base {
|
||||||
|
|
||||||
|
void SetThreadNameImpl(const char* name, usize len) {
|
||||||
|
//COMMON_ASSERT(len <= 15, "name will overflow pthread_setname_np() buffer");
|
||||||
|
pthread_setname_np(pthread_self(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
15
speech2/src/base/Thread.hpp
Normal file
15
speech2/src/base/Thread.hpp
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <base/Types.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace base {
|
||||||
|
|
||||||
|
void SetThreadNameImpl(const char* name, usize len);
|
||||||
|
|
||||||
|
/// Sets the name of the current thread. Mainly for portability.
|
||||||
|
inline void SetThreadName(const std::string& name) {
|
||||||
|
SetThreadNameImpl(name.data(), name.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace collabvm
|
52
speech2/src/base/Types.hpp
Normal file
52
speech2/src/base/Types.hpp
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
using u8 = std::uint8_t;
|
||||||
|
using i8 = std::int8_t;
|
||||||
|
using u16 = std::uint16_t;
|
||||||
|
using i16 = std::int16_t;
|
||||||
|
using u32 = std::uint32_t;
|
||||||
|
using i32 = std::int32_t;
|
||||||
|
using u64 = std::uint64_t;
|
||||||
|
using i64 = std::int64_t;
|
||||||
|
using usize = std::size_t;
|
||||||
|
using isize = std::intptr_t;
|
||||||
|
|
||||||
|
namespace base {
|
||||||
|
|
||||||
|
/// A little ergonomic wrapper over
|
||||||
|
/// std::unique_ptr<T[]>, for a "kinda-vector"
|
||||||
|
/// that lives on the heap and is statically sized
|
||||||
|
template <class T>
|
||||||
|
struct UniqueArray final {
|
||||||
|
explicit UniqueArray(usize size)
|
||||||
|
: array(std::make_unique<T[]>(size)),
|
||||||
|
size(size) {
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueArray(UniqueArray&& move) {
|
||||||
|
array = std::move(move.array);
|
||||||
|
size = move.size;
|
||||||
|
|
||||||
|
// invalidate
|
||||||
|
move.array = nullptr;
|
||||||
|
move.size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
T& operator[](usize index) { return Get()[index]; }
|
||||||
|
const T& operator[](usize index) const { return Get()[index]; }
|
||||||
|
|
||||||
|
T* Get() { return array.get(); }
|
||||||
|
const T* Get() const { return array.get(); }
|
||||||
|
usize Size() const { return size; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<T[]> array {};
|
||||||
|
usize size {};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using Unique = std::unique_ptr<T>;
|
||||||
|
|
||||||
|
} // namespace common
|
35
speech2/src/impl/CMakeLists.txt
Normal file
35
speech2/src/impl/CMakeLists.txt
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
add_library(speech2_impl
|
||||||
|
asio_src.cpp
|
||||||
|
beast_src.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
speech2_target(speech2_impl)
|
||||||
|
|
||||||
|
target_compile_definitions(speech2_impl PUBLIC
|
||||||
|
# Need to force this on, since I think clang's msvc compatibility
|
||||||
|
# is deciding to set a wrong __cplusplus (like MSVC, so it's not *exactly* clang's fault).
|
||||||
|
# The best way to fix it would probably involve using clang-cl frontend and passing the option (I think.)
|
||||||
|
# (nevermind, it's just broken.)
|
||||||
|
-DBOOST_ASIO_HAS_STD_INVOKE_RESULT=1
|
||||||
|
|
||||||
|
# We compile all of these header-only libraries in separate .cpp source files
|
||||||
|
# to decrease build churn
|
||||||
|
-DBOOST_ASIO_SEPARATE_COMPILATION=1
|
||||||
|
-DBOOST_BEAST_SEPARATE_COMPILATION=1
|
||||||
|
|
||||||
|
# Disable deprecated functionality and some things which add additional dependencies or are
|
||||||
|
# simply baggage we aren't ever going to use
|
||||||
|
-DBOOST_ASIO_NO_DEPRECATED=1
|
||||||
|
-DBOOST_ASIO_DISABLE_BOOST_ARRAY=1
|
||||||
|
-DBOOST_ASIO_DISABLE_BOOST_BIND=1
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(speech2_impl PUBLIC
|
||||||
|
Boost::asio
|
||||||
|
Boost::beast
|
||||||
|
|
||||||
|
Boost::coroutine
|
||||||
|
Boost::context
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(speech2::impl ALIAS speech2_impl)
|
5
speech2/src/impl/asio_src.cpp
Normal file
5
speech2/src/impl/asio_src.cpp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Since we're using (BOOST_)ASIO_SEPARATE_COMPILATION, we need
|
||||||
|
// to include the <(boost/)asio/impl/src.hpp> header in some TU.
|
||||||
|
// We use this one to explicitly do so.
|
||||||
|
|
||||||
|
#include <boost/asio/impl/src.hpp>
|
1
speech2/src/impl/beast_src.cpp
Normal file
1
speech2/src/impl/beast_src.cpp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
#include <boost/beast/src.hpp>
|
|
@ -6,70 +6,106 @@
|
||||||
#include <boost/asio/spawn.hpp>
|
#include <boost/asio/spawn.hpp>
|
||||||
#include <boost/asio/steady_timer.hpp>
|
#include <boost/asio/steady_timer.hpp>
|
||||||
#include <boost/asio/write.hpp>
|
#include <boost/asio/write.hpp>
|
||||||
|
#include <boost/beast/core/basic_stream.hpp>
|
||||||
|
#include <boost/beast/core/flat_buffer.hpp>
|
||||||
|
#include <boost/beast/http.hpp>
|
||||||
|
#include <boost/beast/http/message_generator.hpp>
|
||||||
|
#include <boost/beast/http/read.hpp>
|
||||||
|
#include <boost/beast/http/string_body.hpp>
|
||||||
|
#include <boost/beast/http/write.hpp>
|
||||||
|
#include <format>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "base/Logger.hpp"
|
||||||
|
#include "base/StdoutSink.hpp"
|
||||||
#include "speechapi.hpp"
|
#include "speechapi.hpp"
|
||||||
|
|
||||||
using boost::asio::ip::tcp;
|
namespace net = boost::asio;
|
||||||
|
namespace beast = boost::beast;
|
||||||
|
namespace bhttp = beast::http;
|
||||||
|
|
||||||
|
using net::ip::tcp;
|
||||||
|
|
||||||
|
auto coresv_to_cxx(boost::core::string_view sv) {
|
||||||
|
return std::string_view { sv.data(), sv.length() };
|
||||||
|
}
|
||||||
|
|
||||||
|
// A test coro
|
||||||
|
void test_out_of_line_coro(net::any_io_executor ioc,net::yield_context yc) {
|
||||||
|
net::steady_timer t{ioc};
|
||||||
|
|
||||||
|
t.expires_after(std::chrono::seconds(5));
|
||||||
|
t.async_wait(yc);
|
||||||
|
}
|
||||||
|
|
||||||
class session : public std::enable_shared_from_this<session> {
|
class session : public std::enable_shared_from_this<session> {
|
||||||
public:
|
public:
|
||||||
explicit session(boost::asio::io_context& io_context, tcp::socket socket)
|
explicit session(net::io_context& io_context, beast::basic_stream<net::ip::tcp> socket)
|
||||||
: socket_(std::move(socket)), timer_(io_context), strand_(io_context.get_executor()) {}
|
: socket_(std::move(socket)), strand_(io_context.get_executor()) {}
|
||||||
|
|
||||||
void go() {
|
void go() {
|
||||||
auto self(shared_from_this());
|
auto self(shared_from_this());
|
||||||
boost::asio::spawn(
|
|
||||||
|
net::spawn(
|
||||||
strand_,
|
strand_,
|
||||||
[this, self](boost::asio::yield_context yield) {
|
[this, self](net::yield_context yield) {
|
||||||
try {
|
try {
|
||||||
char data[128];
|
auto& logger = base::Logger::Get("HTTPSession");
|
||||||
|
|
||||||
|
bhttp::request<bhttp::string_body> req {};
|
||||||
|
beast::flat_buffer buffer {};
|
||||||
|
|
||||||
|
logger.Info("Wait test");
|
||||||
|
|
||||||
|
// mostly just to test if I can yield stuff from another member function.
|
||||||
|
// This seems to work, so /shrug
|
||||||
|
test_out_of_line_coro(socket_.get_executor(), yield);
|
||||||
|
|
||||||
|
logger.Info("Wait completed");
|
||||||
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
timer_.expires_after(std::chrono::seconds(10));
|
socket_.expires_after(std::chrono::seconds(10));
|
||||||
std::size_t n = socket_.async_read_some(boost::asio::buffer(data), yield);
|
bhttp::async_read(socket_, buffer, req, yield);
|
||||||
boost::asio::async_write(socket_, boost::asio::buffer(data, n), yield);
|
|
||||||
|
auto const routeTest = [&]() -> bhttp::message_generator {
|
||||||
|
bhttp::response<bhttp::string_body> resp { bhttp::status::bad_request, req.version() };
|
||||||
|
resp.set(bhttp::field::server, "Fucker Google/1.0");
|
||||||
|
resp.set(bhttp::field::content_type, "text/plain");
|
||||||
|
resp.keep_alive(false);
|
||||||
|
resp.body() = std::format("You requested \"{} {}\"", coresv_to_cxx(req.method_string()), coresv_to_cxx(req.target()));
|
||||||
|
resp.prepare_payload();
|
||||||
|
return resp;
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.Info("HTTP request to {}", coresv_to_cxx(req.target()));
|
||||||
|
|
||||||
|
auto res = routeTest();
|
||||||
|
|
||||||
|
socket_.expires_after(std::chrono::seconds(10));
|
||||||
|
beast::async_write(socket_, std::move(res), yield);
|
||||||
|
|
||||||
|
if(!req.keep_alive()) {
|
||||||
|
socket_.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch(std::exception& e) {
|
} catch(std::exception& e) {
|
||||||
socket_.close();
|
socket_.close();
|
||||||
timer_.cancel();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
boost::asio::detached);
|
net::detached);
|
||||||
|
|
||||||
boost::asio::spawn(
|
|
||||||
strand_,
|
|
||||||
[this, self](boost::asio::yield_context yield) {
|
|
||||||
while(socket_.is_open()) {
|
|
||||||
boost::system::error_code ignored_ec;
|
|
||||||
timer_.async_wait(yield[ignored_ec]);
|
|
||||||
if(timer_.expiry() <= boost::asio::steady_timer::clock_type::now())
|
|
||||||
socket_.close();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
boost::asio::detached);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
tcp::socket socket_;
|
beast::basic_stream<net::ip::tcp> socket_;
|
||||||
boost::asio::steady_timer timer_;
|
net::strand<net::io_context::executor_type> strand_;
|
||||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
// CoInitialize(nullptr);
|
// CoInitialize(nullptr);
|
||||||
#if 0
|
|
||||||
boost::asio::io_context iocMain(1);
|
|
||||||
|
|
||||||
printf("inited io context\n");
|
base::LoggerAttachStdout();
|
||||||
|
|
||||||
iocMain.post([&]() {
|
|
||||||
printf("Hello from Boost.ASIO + C++20 on XP\n");
|
|
||||||
iocMain.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
iocMain.run();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(argc != 2) {
|
if(argc != 2) {
|
||||||
|
@ -77,19 +113,21 @@ int main(int argc, char** argv) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::asio::io_context io_context;
|
net::io_context io_context;
|
||||||
|
|
||||||
boost::asio::spawn(
|
net::spawn(
|
||||||
io_context,
|
io_context,
|
||||||
[&](boost::asio::yield_context yield) {
|
[&](net::yield_context yield) {
|
||||||
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), std::atoi(argv[1])));
|
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), std::atoi(argv[1])));
|
||||||
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
tcp::socket socket(io_context);
|
tcp::socket socket(io_context);
|
||||||
|
|
||||||
acceptor.async_accept(socket, yield[ec]);
|
acceptor.async_accept(socket, yield[ec]);
|
||||||
if(!ec) {
|
if(!ec) {
|
||||||
std::make_shared<session>(io_context, std::move(socket))->go();
|
auto stream = beast::basic_stream<net::ip::tcp>(std::move(socket));
|
||||||
|
std::make_shared<session>(io_context, std::move(stream))->go();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# SAPI4 layer for speech2
|
||||||
|
|
||||||
add_library(speech2_sapi4
|
add_library(speech2_sapi4
|
||||||
api_sapi4.cpp
|
api_sapi4.cpp
|
||||||
|
@ -6,3 +7,5 @@ add_library(speech2_sapi4
|
||||||
)
|
)
|
||||||
|
|
||||||
speech2_target(speech2_sapi4)
|
speech2_target(speech2_sapi4)
|
||||||
|
|
||||||
|
add_library(speech2::api_sapi4 ALIAS speech2_sapi4)
|
||||||
|
|
1
speech2/third_party/boost/beast
vendored
Submodule
1
speech2/third_party/boost/beast
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 98b8be489fa7a74753a724d8d0772d90bcbed0fc
|
1
speech2/third_party/boost/list
vendored
1
speech2/third_party/boost/list
vendored
|
@ -64,3 +64,4 @@ utility
|
||||||
variant2
|
variant2
|
||||||
winapi
|
winapi
|
||||||
asio
|
asio
|
||||||
|
beast
|
||||||
|
|
Loading…
Reference in a new issue