Compare commits
20 Commits
master
...
vst2host_r
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bc767f41b | |||
|
|
3a84c1dc10 | ||
|
|
24675ba237 | ||
|
|
c3f1664156 | ||
|
|
31b9f459ba | ||
|
|
0bf1673c31 | ||
|
|
38095e67de | ||
|
|
3b236f7a39 | ||
|
|
b7edc41c7a | ||
| 0a65fff8bd | |||
|
|
ec29f5aaf6 | ||
| a03ba378bc | |||
|
|
7dad847277 | ||
|
|
a6d5b3fe32 | ||
|
|
a77ef342cb | ||
|
|
0219717936 | ||
|
|
5266d6429d | ||
|
|
b96404e317 | ||
| 16235e0936 | |||
|
|
bf8ad281db |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ cmake-build-*/
|
|||||||
/build
|
/build
|
||||||
/.vs
|
/.vs
|
||||||
*.DotSettings.user
|
*.DotSettings.user
|
||||||
|
.aider*
|
||||||
|
|||||||
1
src/backend/.gitignore
vendored
Normal file
1
src/backend/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/proto
|
||||||
@@ -26,6 +26,9 @@ include(cmake_script/detect_os.cmake)
|
|||||||
include(cmake_script/plugin_host_register.cmake)
|
include(cmake_script/plugin_host_register.cmake)
|
||||||
include(cmake_script/configure_glfw_native.cmake)
|
include(cmake_script/configure_glfw_native.cmake)
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
# 使用自定义函数来配置项目的编译选项。
|
# 使用自定义函数来配置项目的编译选项。
|
||||||
# @param STANDARD: 指定C++语言标准,此处为 C++23。
|
# @param STANDARD: 指定C++语言标准,此处为 C++23。
|
||||||
# @param INTERFACE_TARGET: 创建一个名为 config_target 的 INTERFACE 目标,
|
# @param INTERFACE_TARGET: 创建一个名为 config_target 的 INTERFACE 目标,
|
||||||
@@ -35,6 +38,8 @@ setup_project_options(
|
|||||||
INTERFACE_TARGET config_target
|
INTERFACE_TARGET config_target
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_definitions(-D_WIN32_WINNT=0x0A00 -DWIN32_LEAN_AND_MEAN -DNOMINMAX)
|
||||||
|
|
||||||
# NOTE: 硬编码构建目录可能会降低灵活性。
|
# NOTE: 硬编码构建目录可能会降低灵活性。
|
||||||
# 通常,更推荐的做法是让用户在调用 CMake 时通过 -B <build_dir> 参数来指定构建目录,
|
# 通常,更推荐的做法是让用户在调用 CMake 时通过 -B <build_dir> 参数来指定构建目录,
|
||||||
# 以支持灵活的 out-of-source builds。
|
# 以支持灵活的 out-of-source builds。
|
||||||
@@ -49,6 +54,8 @@ configure_project_defaults()
|
|||||||
find_package(gRPC CONFIG REQUIRED)
|
find_package(gRPC CONFIG REQUIRED)
|
||||||
find_package(Protobuf CONFIG REQUIRED)
|
find_package(Protobuf CONFIG REQUIRED)
|
||||||
find_package(ZeroMQ CONFIG REQUIRED)
|
find_package(ZeroMQ CONFIG REQUIRED)
|
||||||
|
find_package(spdlog CONFIG REQUIRED)
|
||||||
|
find_package(Boost CONFIG REQUIRED COMPONENTS system interprocess lockfree asio process uuid circular_buffer thread)
|
||||||
|
|
||||||
# --- Protocol Buffers 编译 (Protocol Buffers Compilation) ---
|
# --- Protocol Buffers 编译 (Protocol Buffers Compilation) ---
|
||||||
# 使用自定义函数编译 .proto 文件,自动生成 C++ 源代码和 gRPC 服务代码。
|
# 使用自定义函数编译 .proto 文件,自动生成 C++ 源代码和 gRPC 服务代码。
|
||||||
@@ -58,7 +65,7 @@ find_package(ZeroMQ CONFIG REQUIRED)
|
|||||||
# @param OUTPUT_PATH: 生成的 .pb.h, .pb.cc, .grpc.pb.h, .grpc.pb.cc 文件的输出目录。
|
# @param OUTPUT_PATH: 生成的 .pb.h, .pb.cc, .grpc.pb.h, .grpc.pb.cc 文件的输出目录。
|
||||||
# @param GRPC_ENABLED: 设置为 TRUE,表示同时生成 gRPC 服务相关的代码。
|
# @param GRPC_ENABLED: 设置为 TRUE,表示同时生成 gRPC 服务相关的代码。
|
||||||
compile_proto_files(
|
compile_proto_files(
|
||||||
TARGET_NAME alicho_proto
|
TARGET_NAME AlichoProto
|
||||||
PROTO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../proto
|
PROTO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../proto
|
||||||
OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/proto
|
OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/proto
|
||||||
GRPC_ENABLED TRUE
|
GRPC_ENABLED TRUE
|
||||||
@@ -68,3 +75,4 @@ add_subdirectory(src/misc)
|
|||||||
add_subdirectory(src/vst2_host)
|
add_subdirectory(src/vst2_host)
|
||||||
add_subdirectory(src/vst3_host)
|
add_subdirectory(src/vst3_host)
|
||||||
add_subdirectory(src/engine)
|
add_subdirectory(src/engine)
|
||||||
|
add_subdirectory(tests)
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ function(add_os_definitions target)
|
|||||||
set(alicho_def_arch_64bit 0)
|
set(alicho_def_arch_64bit 0)
|
||||||
set(alicho_def_arch_32bit 0)
|
set(alicho_def_arch_32bit 0)
|
||||||
set(alicho_def_apple 0) # 用于 iOS 和 macOS 的通用定义
|
set(alicho_def_apple 0) # 用于 iOS 和 macOS 的通用定义
|
||||||
|
set(alicho_def_debug 0) # 当前是否处于调试模式
|
||||||
|
|
||||||
|
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
set(alicho_def_debug 1)
|
||||||
|
endif ()
|
||||||
|
|
||||||
# -- 操作系统检测与赋值 --
|
# -- 操作系统检测与赋值 --
|
||||||
# 注意检测顺序:优先检测更具体的平台
|
# 注意检测顺序:优先检测更具体的平台
|
||||||
@@ -113,6 +118,8 @@ function(add_os_definitions target)
|
|||||||
list(APPEND definitions_list "ALICHO_PLATFORM_POSIX=${alicho_def_posix}")
|
list(APPEND definitions_list "ALICHO_PLATFORM_POSIX=${alicho_def_posix}")
|
||||||
list(APPEND definitions_list "ALICHO_PLATFORM_IS_MOBILE=${alicho_def_mobile}")
|
list(APPEND definitions_list "ALICHO_PLATFORM_IS_MOBILE=${alicho_def_mobile}")
|
||||||
|
|
||||||
|
list(APPEND definitions_list "ALICHO_DEBUG=${alicho_def_debug}") # 当前是否处于调试模式
|
||||||
|
|
||||||
# --- 阶段 3: 应用所有定义 ---
|
# --- 阶段 3: 应用所有定义 ---
|
||||||
# **关键:使用一次调用将所有定义添加到目标**
|
# **关键:使用一次调用将所有定义添加到目标**
|
||||||
if(definitions_list) # 确保列表非空
|
if(definitions_list) # 确保列表非空
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function(is_current_platform platform is_match)
|
|||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
set(matches TRUE)
|
set(matches TRUE)
|
||||||
endif()
|
endif()
|
||||||
elseif(platform STREQUAL "mac")
|
elseif(platform STREQUAL "macos")
|
||||||
if(APPLE AND NOT IOS)
|
if(APPLE AND NOT IOS)
|
||||||
set(matches TRUE)
|
set(matches TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -1,649 +0,0 @@
|
|||||||
// Generated by the gRPC C++ plugin.
|
|
||||||
// If you make any local change, they will be lost.
|
|
||||||
// source: daw_api.proto
|
|
||||||
|
|
||||||
#include "daw_api.pb.h"
|
|
||||||
#include "daw_api.grpc.pb.h"
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <grpcpp/support/async_stream.h>
|
|
||||||
#include <grpcpp/support/async_unary_call.h>
|
|
||||||
#include <grpcpp/impl/channel_interface.h>
|
|
||||||
#include <grpcpp/impl/client_unary_call.h>
|
|
||||||
#include <grpcpp/support/client_callback.h>
|
|
||||||
#include <grpcpp/support/message_allocator.h>
|
|
||||||
#include <grpcpp/support/method_handler.h>
|
|
||||||
#include <grpcpp/impl/rpc_service_method.h>
|
|
||||||
#include <grpcpp/support/server_callback.h>
|
|
||||||
#include <grpcpp/impl/server_callback_handlers.h>
|
|
||||||
#include <grpcpp/server_context.h>
|
|
||||||
#include <grpcpp/impl/service_type.h>
|
|
||||||
#include <grpcpp/support/sync_stream.h>
|
|
||||||
namespace daw {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
static const char* TransportService_method_names[] = {
|
|
||||||
"/daw.api.TransportService/Play",
|
|
||||||
"/daw.api.TransportService/Pause",
|
|
||||||
"/daw.api.TransportService/Stop",
|
|
||||||
"/daw.api.TransportService/SetTempo",
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr< TransportService::Stub> TransportService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) {
|
|
||||||
(void)options;
|
|
||||||
std::unique_ptr< TransportService::Stub> stub(new TransportService::Stub(channel, options));
|
|
||||||
return stub;
|
|
||||||
}
|
|
||||||
|
|
||||||
TransportService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options)
|
|
||||||
: channel_(channel), rpcmethod_Play_(TransportService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
, rpcmethod_Pause_(TransportService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
, rpcmethod_Stop_(TransportService_method_names[2], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
, rpcmethod_SetTempo_(TransportService_method_names[3], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
{}
|
|
||||||
|
|
||||||
::grpc::Status TransportService::Stub::Play(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::daw::api::StatusResponse* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Play_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TransportService::Stub::async::Play(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Play_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TransportService::Stub::async::Play(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Play_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::PrepareAsyncPlayRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Play_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::AsyncPlayRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncPlayRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TransportService::Stub::Pause(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::daw::api::StatusResponse* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Pause_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TransportService::Stub::async::Pause(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Pause_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TransportService::Stub::async::Pause(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Pause_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::PrepareAsyncPauseRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Pause_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::AsyncPauseRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncPauseRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TransportService::Stub::Stop(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::daw::api::StatusResponse* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Stop_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TransportService::Stub::async::Stop(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Stop_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TransportService::Stub::async::Stop(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Stop_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::PrepareAsyncStopRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Stop_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::AsyncStopRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncStopRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TransportService::Stub::SetTempo(::grpc::ClientContext* context, const ::daw::api::SetTempoRequest& request, ::daw::api::StatusResponse* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::SetTempoRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SetTempo_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TransportService::Stub::async::SetTempo(::grpc::ClientContext* context, const ::daw::api::SetTempoRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::SetTempoRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTempo_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TransportService::Stub::async::SetTempo(::grpc::ClientContext* context, const ::daw::api::SetTempoRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTempo_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::PrepareAsyncSetTempoRaw(::grpc::ClientContext* context, const ::daw::api::SetTempoRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::SetTempoRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SetTempo_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TransportService::Stub::AsyncSetTempoRaw(::grpc::ClientContext* context, const ::daw::api::SetTempoRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncSetTempoRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TransportService::Service::Service() {
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
TransportService_method_names[0],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< TransportService::Service, ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](TransportService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::Empty* req,
|
|
||||||
::daw::api::StatusResponse* resp) {
|
|
||||||
return service->Play(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
TransportService_method_names[1],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< TransportService::Service, ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](TransportService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::Empty* req,
|
|
||||||
::daw::api::StatusResponse* resp) {
|
|
||||||
return service->Pause(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
TransportService_method_names[2],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< TransportService::Service, ::daw::api::Empty, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](TransportService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::Empty* req,
|
|
||||||
::daw::api::StatusResponse* resp) {
|
|
||||||
return service->Stop(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
TransportService_method_names[3],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< TransportService::Service, ::daw::api::SetTempoRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](TransportService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::SetTempoRequest* req,
|
|
||||||
::daw::api::StatusResponse* resp) {
|
|
||||||
return service->SetTempo(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
TransportService::Service::~Service() {
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TransportService::Service::Play(::grpc::ServerContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TransportService::Service::Pause(::grpc::ServerContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TransportService::Service::Stop(::grpc::ServerContext* context, const ::daw::api::Empty* request, ::daw::api::StatusResponse* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TransportService::Service::SetTempo(::grpc::ServerContext* context, const ::daw::api::SetTempoRequest* request, ::daw::api::StatusResponse* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static const char* ProjectService_method_names[] = {
|
|
||||||
"/daw.api.ProjectService/NewProject",
|
|
||||||
"/daw.api.ProjectService/LoadProject",
|
|
||||||
"/daw.api.ProjectService/SaveProject",
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr< ProjectService::Stub> ProjectService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) {
|
|
||||||
(void)options;
|
|
||||||
std::unique_ptr< ProjectService::Stub> stub(new ProjectService::Stub(channel, options));
|
|
||||||
return stub;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options)
|
|
||||||
: channel_(channel), rpcmethod_NewProject_(ProjectService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
, rpcmethod_LoadProject_(ProjectService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
, rpcmethod_SaveProject_(ProjectService_method_names[2], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
{}
|
|
||||||
|
|
||||||
::grpc::Status ProjectService::Stub::NewProject(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::daw::api::ProjectState* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::Empty, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_NewProject_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectService::Stub::async::NewProject(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::ProjectState* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::Empty, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_NewProject_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectService::Stub::async::NewProject(::grpc::ClientContext* context, const ::daw::api::Empty* request, ::daw::api::ProjectState* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_NewProject_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::ProjectState>* ProjectService::Stub::PrepareAsyncNewProjectRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::ProjectState, ::daw::api::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_NewProject_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::ProjectState>* ProjectService::Stub::AsyncNewProjectRaw(::grpc::ClientContext* context, const ::daw::api::Empty& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncNewProjectRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status ProjectService::Stub::LoadProject(::grpc::ClientContext* context, const ::daw::api::LoadProjectRequest& request, ::daw::api::ProjectState* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::LoadProjectRequest, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_LoadProject_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectService::Stub::async::LoadProject(::grpc::ClientContext* context, const ::daw::api::LoadProjectRequest* request, ::daw::api::ProjectState* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::LoadProjectRequest, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_LoadProject_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectService::Stub::async::LoadProject(::grpc::ClientContext* context, const ::daw::api::LoadProjectRequest* request, ::daw::api::ProjectState* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_LoadProject_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::ProjectState>* ProjectService::Stub::PrepareAsyncLoadProjectRaw(::grpc::ClientContext* context, const ::daw::api::LoadProjectRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::ProjectState, ::daw::api::LoadProjectRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_LoadProject_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::ProjectState>* ProjectService::Stub::AsyncLoadProjectRaw(::grpc::ClientContext* context, const ::daw::api::LoadProjectRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncLoadProjectRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status ProjectService::Stub::SaveProject(::grpc::ClientContext* context, const ::daw::api::SaveProjectRequest& request, ::daw::api::StatusResponse* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::SaveProjectRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SaveProject_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectService::Stub::async::SaveProject(::grpc::ClientContext* context, const ::daw::api::SaveProjectRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::SaveProjectRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SaveProject_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectService::Stub::async::SaveProject(::grpc::ClientContext* context, const ::daw::api::SaveProjectRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SaveProject_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* ProjectService::Stub::PrepareAsyncSaveProjectRaw(::grpc::ClientContext* context, const ::daw::api::SaveProjectRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::SaveProjectRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SaveProject_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* ProjectService::Stub::AsyncSaveProjectRaw(::grpc::ClientContext* context, const ::daw::api::SaveProjectRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncSaveProjectRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectService::Service::Service() {
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
ProjectService_method_names[0],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< ProjectService::Service, ::daw::api::Empty, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](ProjectService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::Empty* req,
|
|
||||||
::daw::api::ProjectState* resp) {
|
|
||||||
return service->NewProject(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
ProjectService_method_names[1],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< ProjectService::Service, ::daw::api::LoadProjectRequest, ::daw::api::ProjectState, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](ProjectService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::LoadProjectRequest* req,
|
|
||||||
::daw::api::ProjectState* resp) {
|
|
||||||
return service->LoadProject(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
ProjectService_method_names[2],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< ProjectService::Service, ::daw::api::SaveProjectRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](ProjectService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::SaveProjectRequest* req,
|
|
||||||
::daw::api::StatusResponse* resp) {
|
|
||||||
return service->SaveProject(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectService::Service::~Service() {
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status ProjectService::Service::NewProject(::grpc::ServerContext* context, const ::daw::api::Empty* request, ::daw::api::ProjectState* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status ProjectService::Service::LoadProject(::grpc::ServerContext* context, const ::daw::api::LoadProjectRequest* request, ::daw::api::ProjectState* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status ProjectService::Service::SaveProject(::grpc::ServerContext* context, const ::daw::api::SaveProjectRequest* request, ::daw::api::StatusResponse* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static const char* TrackService_method_names[] = {
|
|
||||||
"/daw.api.TrackService/AddTrack",
|
|
||||||
"/daw.api.TrackService/RemoveTrack",
|
|
||||||
"/daw.api.TrackService/SetTrackVolume",
|
|
||||||
"/daw.api.TrackService/SetTrackPan",
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr< TrackService::Stub> TrackService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) {
|
|
||||||
(void)options;
|
|
||||||
std::unique_ptr< TrackService::Stub> stub(new TrackService::Stub(channel, options));
|
|
||||||
return stub;
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options)
|
|
||||||
: channel_(channel), rpcmethod_AddTrack_(TrackService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
, rpcmethod_RemoveTrack_(TrackService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
, rpcmethod_SetTrackVolume_(TrackService_method_names[2], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
, rpcmethod_SetTrackPan_(TrackService_method_names[3], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
{}
|
|
||||||
|
|
||||||
::grpc::Status TrackService::Stub::AddTrack(::grpc::ClientContext* context, const ::daw::api::AddTrackRequest& request, ::daw::api::TrackInfo* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::AddTrackRequest, ::daw::api::TrackInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_AddTrack_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackService::Stub::async::AddTrack(::grpc::ClientContext* context, const ::daw::api::AddTrackRequest* request, ::daw::api::TrackInfo* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::AddTrackRequest, ::daw::api::TrackInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_AddTrack_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackService::Stub::async::AddTrack(::grpc::ClientContext* context, const ::daw::api::AddTrackRequest* request, ::daw::api::TrackInfo* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_AddTrack_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::TrackInfo>* TrackService::Stub::PrepareAsyncAddTrackRaw(::grpc::ClientContext* context, const ::daw::api::AddTrackRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::TrackInfo, ::daw::api::AddTrackRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_AddTrack_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::TrackInfo>* TrackService::Stub::AsyncAddTrackRaw(::grpc::ClientContext* context, const ::daw::api::AddTrackRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncAddTrackRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TrackService::Stub::RemoveTrack(::grpc::ClientContext* context, const ::daw::api::TrackIdRequest& request, ::daw::api::StatusResponse* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::TrackIdRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_RemoveTrack_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackService::Stub::async::RemoveTrack(::grpc::ClientContext* context, const ::daw::api::TrackIdRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::TrackIdRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_RemoveTrack_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackService::Stub::async::RemoveTrack(::grpc::ClientContext* context, const ::daw::api::TrackIdRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_RemoveTrack_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::PrepareAsyncRemoveTrackRaw(::grpc::ClientContext* context, const ::daw::api::TrackIdRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::TrackIdRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_RemoveTrack_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::AsyncRemoveTrackRaw(::grpc::ClientContext* context, const ::daw::api::TrackIdRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncRemoveTrackRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TrackService::Stub::SetTrackVolume(::grpc::ClientContext* context, const ::daw::api::SetTrackVolumeRequest& request, ::daw::api::StatusResponse* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::SetTrackVolumeRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SetTrackVolume_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackService::Stub::async::SetTrackVolume(::grpc::ClientContext* context, const ::daw::api::SetTrackVolumeRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::SetTrackVolumeRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTrackVolume_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackService::Stub::async::SetTrackVolume(::grpc::ClientContext* context, const ::daw::api::SetTrackVolumeRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTrackVolume_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::PrepareAsyncSetTrackVolumeRaw(::grpc::ClientContext* context, const ::daw::api::SetTrackVolumeRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::SetTrackVolumeRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SetTrackVolume_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::AsyncSetTrackVolumeRaw(::grpc::ClientContext* context, const ::daw::api::SetTrackVolumeRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncSetTrackVolumeRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TrackService::Stub::SetTrackPan(::grpc::ClientContext* context, const ::daw::api::SetTrackPanRequest& request, ::daw::api::StatusResponse* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::SetTrackPanRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SetTrackPan_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackService::Stub::async::SetTrackPan(::grpc::ClientContext* context, const ::daw::api::SetTrackPanRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::SetTrackPanRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTrackPan_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackService::Stub::async::SetTrackPan(::grpc::ClientContext* context, const ::daw::api::SetTrackPanRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetTrackPan_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::PrepareAsyncSetTrackPanRaw(::grpc::ClientContext* context, const ::daw::api::SetTrackPanRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::SetTrackPanRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SetTrackPan_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* TrackService::Stub::AsyncSetTrackPanRaw(::grpc::ClientContext* context, const ::daw::api::SetTrackPanRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncSetTrackPanRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackService::Service::Service() {
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
TrackService_method_names[0],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< TrackService::Service, ::daw::api::AddTrackRequest, ::daw::api::TrackInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](TrackService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::AddTrackRequest* req,
|
|
||||||
::daw::api::TrackInfo* resp) {
|
|
||||||
return service->AddTrack(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
TrackService_method_names[1],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< TrackService::Service, ::daw::api::TrackIdRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](TrackService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::TrackIdRequest* req,
|
|
||||||
::daw::api::StatusResponse* resp) {
|
|
||||||
return service->RemoveTrack(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
TrackService_method_names[2],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< TrackService::Service, ::daw::api::SetTrackVolumeRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](TrackService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::SetTrackVolumeRequest* req,
|
|
||||||
::daw::api::StatusResponse* resp) {
|
|
||||||
return service->SetTrackVolume(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
TrackService_method_names[3],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< TrackService::Service, ::daw::api::SetTrackPanRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](TrackService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::SetTrackPanRequest* req,
|
|
||||||
::daw::api::StatusResponse* resp) {
|
|
||||||
return service->SetTrackPan(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackService::Service::~Service() {
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TrackService::Service::AddTrack(::grpc::ServerContext* context, const ::daw::api::AddTrackRequest* request, ::daw::api::TrackInfo* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TrackService::Service::RemoveTrack(::grpc::ServerContext* context, const ::daw::api::TrackIdRequest* request, ::daw::api::StatusResponse* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TrackService::Service::SetTrackVolume(::grpc::ServerContext* context, const ::daw::api::SetTrackVolumeRequest* request, ::daw::api::StatusResponse* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status TrackService::Service::SetTrackPan(::grpc::ServerContext* context, const ::daw::api::SetTrackPanRequest* request, ::daw::api::StatusResponse* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static const char* PluginService_method_names[] = {
|
|
||||||
"/daw.api.PluginService/LoadPlugin",
|
|
||||||
"/daw.api.PluginService/SetPluginParameter",
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr< PluginService::Stub> PluginService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) {
|
|
||||||
(void)options;
|
|
||||||
std::unique_ptr< PluginService::Stub> stub(new PluginService::Stub(channel, options));
|
|
||||||
return stub;
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options)
|
|
||||||
: channel_(channel), rpcmethod_LoadPlugin_(PluginService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
, rpcmethod_SetPluginParameter_(PluginService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
|
||||||
{}
|
|
||||||
|
|
||||||
::grpc::Status PluginService::Stub::LoadPlugin(::grpc::ClientContext* context, const ::daw::api::LoadPluginRequest& request, ::daw::api::PluginInfo* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::LoadPluginRequest, ::daw::api::PluginInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_LoadPlugin_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginService::Stub::async::LoadPlugin(::grpc::ClientContext* context, const ::daw::api::LoadPluginRequest* request, ::daw::api::PluginInfo* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::LoadPluginRequest, ::daw::api::PluginInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_LoadPlugin_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginService::Stub::async::LoadPlugin(::grpc::ClientContext* context, const ::daw::api::LoadPluginRequest* request, ::daw::api::PluginInfo* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_LoadPlugin_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::PluginInfo>* PluginService::Stub::PrepareAsyncLoadPluginRaw(::grpc::ClientContext* context, const ::daw::api::LoadPluginRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::PluginInfo, ::daw::api::LoadPluginRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_LoadPlugin_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::PluginInfo>* PluginService::Stub::AsyncLoadPluginRaw(::grpc::ClientContext* context, const ::daw::api::LoadPluginRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncLoadPluginRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status PluginService::Stub::SetPluginParameter(::grpc::ClientContext* context, const ::daw::api::SetPluginParameterRequest& request, ::daw::api::StatusResponse* response) {
|
|
||||||
return ::grpc::internal::BlockingUnaryCall< ::daw::api::SetPluginParameterRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SetPluginParameter_, context, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginService::Stub::async::SetPluginParameter(::grpc::ClientContext* context, const ::daw::api::SetPluginParameterRequest* request, ::daw::api::StatusResponse* response, std::function<void(::grpc::Status)> f) {
|
|
||||||
::grpc::internal::CallbackUnaryCall< ::daw::api::SetPluginParameterRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetPluginParameter_, context, request, response, std::move(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginService::Stub::async::SetPluginParameter(::grpc::ClientContext* context, const ::daw::api::SetPluginParameterRequest* request, ::daw::api::StatusResponse* response, ::grpc::ClientUnaryReactor* reactor) {
|
|
||||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetPluginParameter_, context, request, response, reactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* PluginService::Stub::PrepareAsyncSetPluginParameterRaw(::grpc::ClientContext* context, const ::daw::api::SetPluginParameterRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::daw::api::StatusResponse, ::daw::api::SetPluginParameterRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SetPluginParameter_, context, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::ClientAsyncResponseReader< ::daw::api::StatusResponse>* PluginService::Stub::AsyncSetPluginParameterRaw(::grpc::ClientContext* context, const ::daw::api::SetPluginParameterRequest& request, ::grpc::CompletionQueue* cq) {
|
|
||||||
auto* result =
|
|
||||||
this->PrepareAsyncSetPluginParameterRaw(context, request, cq);
|
|
||||||
result->StartCall();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginService::Service::Service() {
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
PluginService_method_names[0],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< PluginService::Service, ::daw::api::LoadPluginRequest, ::daw::api::PluginInfo, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](PluginService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::LoadPluginRequest* req,
|
|
||||||
::daw::api::PluginInfo* resp) {
|
|
||||||
return service->LoadPlugin(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
|
||||||
PluginService_method_names[1],
|
|
||||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
|
||||||
new ::grpc::internal::RpcMethodHandler< PluginService::Service, ::daw::api::SetPluginParameterRequest, ::daw::api::StatusResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
|
||||||
[](PluginService::Service* service,
|
|
||||||
::grpc::ServerContext* ctx,
|
|
||||||
const ::daw::api::SetPluginParameterRequest* req,
|
|
||||||
::daw::api::StatusResponse* resp) {
|
|
||||||
return service->SetPluginParameter(ctx, req, resp);
|
|
||||||
}, this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginService::Service::~Service() {
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status PluginService::Service::LoadPlugin(::grpc::ServerContext* context, const ::daw::api::LoadPluginRequest* request, ::daw::api::PluginInfo* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
::grpc::Status PluginService::Service::SetPluginParameter(::grpc::ServerContext* context, const ::daw::api::SetPluginParameterRequest* request, ::daw::api::StatusResponse* response) {
|
|
||||||
(void) context;
|
|
||||||
(void) request;
|
|
||||||
(void) response;
|
|
||||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace daw
|
|
||||||
} // namespace api
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
|
|||||||
# @param ${PROJECT_NAME}: 目标名称。
|
# @param ${PROJECT_NAME}: 目标名称。
|
||||||
# @param ${SRC_FILES}: 构成该目标的所有源文件。
|
# @param ${SRC_FILES}: 构成该目标的所有源文件。
|
||||||
add_executable(${PROJECT_NAME} ${SRC_FILES})
|
add_executable(${PROJECT_NAME} ${SRC_FILES})
|
||||||
add_plugin_host_dependency(${PROJECT_NAME})
|
#add_plugin_host_dependency(${PROJECT_NAME})
|
||||||
|
|
||||||
# 将依赖库链接到我们的可执行文件目标。
|
# 将依赖库链接到我们的可执行文件目标。
|
||||||
# @param ${PROJECT_NAME}: 要链接的目标。
|
# @param ${PROJECT_NAME}: 要链接的目标。
|
||||||
@@ -30,5 +30,9 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
|
|||||||
gRPC::grpc++
|
gRPC::grpc++
|
||||||
protobuf::libprotobuf
|
protobuf::libprotobuf
|
||||||
libzmq
|
libzmq
|
||||||
alicho_proto
|
AlichoProto
|
||||||
|
AlichoMisc
|
||||||
|
ws2_32
|
||||||
|
Boost::process
|
||||||
)
|
)
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
|
|||||||
1
src/backend/src/engine/src/grpc/engine_grpc.cpp
Normal file
1
src/backend/src/engine/src/grpc/engine_grpc.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "engine_grpc.h"
|
||||||
96
src/backend/src/engine/src/grpc/engine_grpc.h
Normal file
96
src/backend/src/engine/src/grpc/engine_grpc.h
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ctrl/frontend_to_engine.grpc.pb.h"
|
||||||
|
|
||||||
|
class daw_api_project_service : public daw::api::ProjectService::Service {
|
||||||
|
public:
|
||||||
|
grpc::Status NewProject(grpc::ServerContext* context,
|
||||||
|
const google::protobuf::Empty* request,
|
||||||
|
daw::api::ProjectState* response) override {
|
||||||
|
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc::Status LoadProject(grpc::ServerContext* context,
|
||||||
|
const daw::api::LoadProjectRequest* request,
|
||||||
|
daw::api::ProjectState* response) override {
|
||||||
|
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc::Status SaveProject(grpc::ServerContext* context,
|
||||||
|
const daw::api::SaveProjectRequest* request,
|
||||||
|
daw::api::StatusResponse* response) override {
|
||||||
|
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class daw_api_transport_service : public daw::api::TransportService::Service {
|
||||||
|
public:
|
||||||
|
virtual ~daw_api_transport_service() override;
|
||||||
|
|
||||||
|
virtual grpc::Status Play(grpc::ServerContext* context,
|
||||||
|
const google::protobuf::Empty* request,
|
||||||
|
daw::api::StatusResponse* response) override;
|
||||||
|
|
||||||
|
virtual grpc::Status Pause(grpc::ServerContext* context,
|
||||||
|
const google::protobuf::Empty* request,
|
||||||
|
daw::api::StatusResponse* response) override;
|
||||||
|
|
||||||
|
virtual grpc::Status Stop(grpc::ServerContext* context,
|
||||||
|
const google::protobuf::Empty* request,
|
||||||
|
daw::api::StatusResponse* response) override;
|
||||||
|
|
||||||
|
virtual grpc::Status SetTempo(grpc::ServerContext* context,
|
||||||
|
const daw::api::SetTempoRequest* request,
|
||||||
|
daw::api::StatusResponse* response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class daw_api_track_service : public daw::api::TrackService::Service {
|
||||||
|
public:
|
||||||
|
grpc::Status AddTrack(grpc::ServerContext* context,
|
||||||
|
const daw::api::AddTrackRequest* request,
|
||||||
|
daw::api::TrackInfo* response) override {
|
||||||
|
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc::Status RemoveTrack(grpc::ServerContext* context,
|
||||||
|
const daw::api::TrackIdRequest* request,
|
||||||
|
daw::api::StatusResponse* response) override {
|
||||||
|
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc::Status SetTrackVolume(grpc::ServerContext* context,
|
||||||
|
const daw::api::SetTrackVolumeRequest* request,
|
||||||
|
daw::api::StatusResponse* response) override {
|
||||||
|
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc::Status SetTrackPan(grpc::ServerContext* context,
|
||||||
|
const daw::api::SetTrackPanRequest* request,
|
||||||
|
daw::api::StatusResponse* response) override {
|
||||||
|
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class daw_api_plugin_service : public daw::api::PluginService::Service {
|
||||||
|
public:
|
||||||
|
grpc::Status LoadPlugin(grpc::ServerContext* context,
|
||||||
|
const daw::api::LoadPluginRequest* request,
|
||||||
|
daw::api::PluginInfo* response) override {
|
||||||
|
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc::Status SetPluginParameter(grpc::ServerContext* context,
|
||||||
|
const daw::api::SetPluginParameterRequest* request,
|
||||||
|
daw::api::StatusResponse* response) override {
|
||||||
|
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
};
|
||||||
1
src/backend/src/engine/src/host/plugin_host.cpp
Normal file
1
src/backend/src/engine/src/host/plugin_host.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "plugin_host.h"
|
||||||
34
src/backend/src/engine/src/host/plugin_host.h
Normal file
34
src/backend/src/engine/src/host/plugin_host.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include "midi_type.h"
|
||||||
|
|
||||||
|
class plugin_host {
|
||||||
|
public:
|
||||||
|
virtual void process(std::span<float*> in_buffers, std::span<float*> out_buffers, int32_t in_frames) = 0;
|
||||||
|
virtual void process(std::span<double*> in_buffers, std::span<double*> out_buffers, int32_t in_frames) = 0;
|
||||||
|
|
||||||
|
virtual auto send_events(std::span<midi_type::midi_event> ev) -> bool = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto get_name() -> std::string = 0;
|
||||||
|
[[nodiscard]] virtual auto get_vendor() -> std::string = 0;
|
||||||
|
[[nodiscard]] virtual auto get_version() -> std::string = 0;
|
||||||
|
[[nodiscard]] virtual auto get_product() -> std::string = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto get_input_count() const noexcept -> int = 0;
|
||||||
|
[[nodiscard]] virtual auto get_output_count() const noexcept -> int = 0;
|
||||||
|
[[nodiscard]] virtual auto get_parameter_count() const noexcept -> int = 0;
|
||||||
|
[[nodiscard]] virtual auto can_double() const noexcept -> bool = 0;
|
||||||
|
[[nodiscard]] virtual auto is_synth() const noexcept -> bool = 0;
|
||||||
|
[[nodiscard]] virtual auto get_unique_id() const noexcept -> int = 0;
|
||||||
|
[[nodiscard]] virtual auto is_valid() const noexcept -> bool = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto get_parameter(int idx) -> float = 0;
|
||||||
|
virtual void set_parameter(int idx, float v) = 0;
|
||||||
|
virtual void set_sample_rate(float in_sample_rate) = 0;
|
||||||
|
virtual void set_block_size(int32_t in_block_size) = 0;
|
||||||
|
[[nodiscard]] virtual auto get_program_name() -> std::string = 0;
|
||||||
|
virtual void set_program_name(const std::string& name) = 0;
|
||||||
|
virtual void set_program(int idx) = 0;
|
||||||
|
[[nodiscard]] virtual auto get_program() -> int32_t = 0;
|
||||||
|
};
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
// VST Plug-Ins SDK
|
||||||
|
// Version 2.4 $Date: 2006/06/20 17:22:55 $
|
||||||
|
//
|
||||||
|
// Category : VST 2.x Interfaces
|
||||||
|
// Filename : aeffect.h
|
||||||
|
// Created by : Steinberg Media Technologies
|
||||||
|
// Description : Definition of AEffect structure
|
||||||
|
//
|
||||||
|
// © 2006, Steinberg Media Technologies, All Rights Reserved
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifndef __aeffect__
|
||||||
|
#define __aeffect__
|
||||||
|
|
||||||
|
// gcc based compiler, or CodeWarrior on Mac OS X
|
||||||
|
#if ((defined(__GNUC__) && (defined(__APPLE_CPP__) || defined(__APPLE_CC__))) || (defined (__MWERKS__) && defined (__MACH__)))
|
||||||
|
#ifndef TARGET_API_MAC_CARBON
|
||||||
|
#define TARGET_API_MAC_CARBON 1
|
||||||
|
#endif
|
||||||
|
#if __ppc__
|
||||||
|
#ifndef VST_FORCE_DEPRECATED
|
||||||
|
#define VST_FORCE_DEPRECATED 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if TARGET_API_MAC_CARBON
|
||||||
|
#ifdef __LP64__
|
||||||
|
#pragma options align=power
|
||||||
|
#else
|
||||||
|
#pragma options align=mac68k
|
||||||
|
#endif
|
||||||
|
#define VSTCALLBACK
|
||||||
|
#elif defined __BORLANDC__
|
||||||
|
#pragma -a8
|
||||||
|
#elif defined(__GNUC__)
|
||||||
|
#pragma pack(push,8)
|
||||||
|
#define VSTCALLBACK __cdecl
|
||||||
|
#elif defined(WIN32) || defined(__FLAT__) || defined CBUILDER
|
||||||
|
#pragma pack(push)
|
||||||
|
#pragma pack(8)
|
||||||
|
#define VSTCALLBACK __cdecl
|
||||||
|
#else
|
||||||
|
#define VSTCALLBACK
|
||||||
|
#endif
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include <string.h> // for strncpy
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
// VST Version
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Define SDK Version (you can generate different versions (from 2.0 to 2.4) of this SDK by setting the unwanted extensions to 0). */
|
||||||
|
#define VST_2_1_EXTENSIONS 1 ///< Version 2.1 extensions (08-06-2000)
|
||||||
|
#define VST_2_2_EXTENSIONS 1 ///< Version 2.2 extensions (08-06-2001)
|
||||||
|
#define VST_2_3_EXTENSIONS 1 ///< Version 2.3 extensions (20-05-2003)
|
||||||
|
#ifndef VST_2_4_EXTENSIONS
|
||||||
|
#define VST_2_4_EXTENSIONS 1 ///< Version 2.4 extensions (01-01-2006)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Current VST Version */
|
||||||
|
#if VST_2_4_EXTENSIONS
|
||||||
|
#define kVstVersion 2400
|
||||||
|
#elif VST_2_3_EXTENSIONS
|
||||||
|
#define kVstVersion 2300
|
||||||
|
#elif VST_2_2_EXTENSIONS
|
||||||
|
#define kVstVersion 2200
|
||||||
|
#elif VST_2_1_EXTENSIONS
|
||||||
|
#define kVstVersion 2100
|
||||||
|
#else
|
||||||
|
#define kVstVersion 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Disable for Hosts to serve Plug-ins below VST 2.4 */
|
||||||
|
#ifndef VST_FORCE_DEPRECATED
|
||||||
|
#define VST_FORCE_DEPRECATED VST_2_4_EXTENSIONS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Declares identifier as deprecated. */
|
||||||
|
#if VST_FORCE_DEPRECATED
|
||||||
|
#define DECLARE_VST_DEPRECATED(identifier) __##identifier##Deprecated
|
||||||
|
#else
|
||||||
|
#define DECLARE_VST_DEPRECATED(identifier) identifier
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Define for 64 Bit Platform. */
|
||||||
|
#ifndef VST_64BIT_PLATFORM
|
||||||
|
#define VST_64BIT_PLATFORM _WIN64 || __LP64__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
// Integral Types
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
typedef short VstInt16; ///< 16 bit integer type
|
||||||
|
typedef int VstInt32; ///< 32 bit integer type
|
||||||
|
typedef __int64 VstInt64; ///< 64 bit integer type
|
||||||
|
#else
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef int16_t VstInt16; ///< 16 bit integer type
|
||||||
|
typedef int32_t VstInt32; ///< 32 bit integer type
|
||||||
|
typedef int64_t VstInt64; ///< 64 bit integer type
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
// Generic Types
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#if VST_64BIT_PLATFORM
|
||||||
|
typedef VstInt64 VstIntPtr; ///< platform-dependent integer type, same size as pointer
|
||||||
|
#else
|
||||||
|
typedef VstInt32 VstIntPtr; ///< platform-dependent integer type, same size as pointer
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
// Misc. Definition
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
#undef CCONST
|
||||||
|
struct AEffect;
|
||||||
|
|
||||||
|
/// @cond ignore
|
||||||
|
typedef VstIntPtr (VSTCALLBACK *audioMasterCallback) (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt);
|
||||||
|
typedef VstIntPtr (VSTCALLBACK *AEffectDispatcherProc) (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt);
|
||||||
|
typedef void (VSTCALLBACK *AEffectProcessProc) (AEffect* effect, float** inputs, float** outputs, VstInt32 sampleFrames);
|
||||||
|
typedef void (VSTCALLBACK *AEffectProcessDoubleProc) (AEffect* effect, double** inputs, double** outputs, VstInt32 sampleFrames);
|
||||||
|
typedef void (VSTCALLBACK *AEffectSetParameterProc) (AEffect* effect, VstInt32 index, float parameter);
|
||||||
|
typedef float (VSTCALLBACK *AEffectGetParameterProc) (AEffect* effect, VstInt32 index);
|
||||||
|
/// @endcond
|
||||||
|
|
||||||
|
/** Four Character Constant (for AEffect->uniqueID) */
|
||||||
|
#define CCONST(a, b, c, d) \
|
||||||
|
((((VstInt32)a) << 24) | (((VstInt32)b) << 16) | (((VstInt32)c) << 8) | (((VstInt32)d) << 0))
|
||||||
|
|
||||||
|
/** AEffect magic number */
|
||||||
|
#define kEffectMagic CCONST ('V', 's', 't', 'P')
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** Basic VST Effect "C" Interface. */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
struct AEffect
|
||||||
|
{
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
VstInt32 magic; ///< must be #kEffectMagic ('VstP')
|
||||||
|
|
||||||
|
/** Host to Plug-in dispatcher @see AudioEffect::dispatcher */
|
||||||
|
AEffectDispatcherProc dispatcher;
|
||||||
|
|
||||||
|
/** \deprecated Accumulating process mode is deprecated in VST 2.4! Use AEffect::processReplacing instead! */
|
||||||
|
AEffectProcessProc DECLARE_VST_DEPRECATED (process);
|
||||||
|
|
||||||
|
/** Set new value of automatable parameter @see AudioEffect::setParameter */
|
||||||
|
AEffectSetParameterProc setParameter;
|
||||||
|
|
||||||
|
/** Returns current value of automatable parameter @see AudioEffect::getParameter*/
|
||||||
|
AEffectGetParameterProc getParameter;
|
||||||
|
|
||||||
|
VstInt32 numPrograms; ///< number of programs
|
||||||
|
VstInt32 numParams; ///< all programs are assumed to have numParams parameters
|
||||||
|
VstInt32 numInputs; ///< number of audio inputs
|
||||||
|
VstInt32 numOutputs; ///< number of audio outputs
|
||||||
|
|
||||||
|
VstInt32 flags; ///< @see VstAEffectFlags
|
||||||
|
|
||||||
|
VstIntPtr resvd1; ///< reserved for Host, must be 0
|
||||||
|
VstIntPtr resvd2; ///< reserved for Host, must be 0
|
||||||
|
|
||||||
|
VstInt32 initialDelay; ///< for algorithms which need input in the first place (Group delay or latency in Samples). This value should be initialized in a resume state.
|
||||||
|
|
||||||
|
VstInt32 DECLARE_VST_DEPRECATED (realQualities); ///< \deprecated unused member
|
||||||
|
VstInt32 DECLARE_VST_DEPRECATED (offQualities); ///< \deprecated unused member
|
||||||
|
float DECLARE_VST_DEPRECATED (ioRatio); ///< \deprecated unused member
|
||||||
|
|
||||||
|
void* object; ///< #AudioEffect class pointer
|
||||||
|
void* user; ///< user-defined pointer
|
||||||
|
|
||||||
|
VstInt32 uniqueID; ///< registered unique identifier (register it at Steinberg 3rd party support Web). This is used to identify a plug-in during save+load of preset and project.
|
||||||
|
VstInt32 version; ///< plug-in version (example 1100 for version 1.1.0.0)
|
||||||
|
|
||||||
|
/** Process audio samples in replacing mode @see AudioEffect::processReplacing */
|
||||||
|
AEffectProcessProc processReplacing;
|
||||||
|
|
||||||
|
#if VST_2_4_EXTENSIONS
|
||||||
|
/** Process double-precision audio samples in replacing mode @see AudioEffect::processDoubleReplacing */
|
||||||
|
AEffectProcessDoubleProc processDoubleReplacing;
|
||||||
|
|
||||||
|
char future[56]; ///< reserved for future use (please zero)
|
||||||
|
#else
|
||||||
|
char future[60]; ///< reserved for future use (please zero)
|
||||||
|
#endif
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
};
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** AEffect flags */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
enum VstAEffectFlags
|
||||||
|
{
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
effFlagsHasEditor = 1 << 0, ///< set if the plug-in provides a custom editor
|
||||||
|
effFlagsCanReplacing = 1 << 4, ///< supports replacing process mode (which should the default mode in VST 2.4)
|
||||||
|
effFlagsProgramChunks = 1 << 5, ///< program data is handled in formatless chunks
|
||||||
|
effFlagsIsSynth = 1 << 8, ///< plug-in is a synth (VSTi), Host may assign mixer channels for its outputs
|
||||||
|
effFlagsNoSoundInStop = 1 << 9, ///< plug-in does not produce sound when input is all silence
|
||||||
|
|
||||||
|
#if VST_2_4_EXTENSIONS
|
||||||
|
effFlagsCanDoubleReplacing = 1 << 12, ///< plug-in supports double precision processing
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DECLARE_VST_DEPRECATED (effFlagsHasClip) = 1 << 1, ///< \deprecated deprecated in VST 2.4
|
||||||
|
DECLARE_VST_DEPRECATED (effFlagsHasVu) = 1 << 2, ///< \deprecated deprecated in VST 2.4
|
||||||
|
DECLARE_VST_DEPRECATED (effFlagsCanMono) = 1 << 3, ///< \deprecated deprecated in VST 2.4
|
||||||
|
DECLARE_VST_DEPRECATED (effFlagsExtIsAsync) = 1 << 10, ///< \deprecated deprecated in VST 2.4
|
||||||
|
DECLARE_VST_DEPRECATED (effFlagsExtHasBuffer) = 1 << 11 ///< \deprecated deprecated in VST 2.4
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
};
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** Basic dispatcher Opcodes (Host to Plug-in) */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
enum AEffectOpcodes
|
||||||
|
{
|
||||||
|
effOpen = 0, ///< no arguments @see AudioEffect::open
|
||||||
|
effClose, ///< no arguments @see AudioEffect::close
|
||||||
|
|
||||||
|
effSetProgram, ///< [value]: new program number @see AudioEffect::setProgram
|
||||||
|
effGetProgram, ///< [return value]: current program number @see AudioEffect::getProgram
|
||||||
|
effSetProgramName, ///< [ptr]: char* with new program name, limited to #kVstMaxProgNameLen @see AudioEffect::setProgramName
|
||||||
|
effGetProgramName, ///< [ptr]: char buffer for current program name, limited to #kVstMaxProgNameLen @see AudioEffect::getProgramName
|
||||||
|
|
||||||
|
effGetParamLabel, ///< [ptr]: char buffer for parameter label, limited to #kVstMaxParamStrLen @see AudioEffect::getParameterLabel
|
||||||
|
effGetParamDisplay, ///< [ptr]: char buffer for parameter display, limited to #kVstMaxParamStrLen @see AudioEffect::getParameterDisplay
|
||||||
|
effGetParamName, ///< [ptr]: char buffer for parameter name, limited to #kVstMaxParamStrLen @see AudioEffect::getParameterName
|
||||||
|
|
||||||
|
DECLARE_VST_DEPRECATED (effGetVu), ///< \deprecated deprecated in VST 2.4
|
||||||
|
|
||||||
|
effSetSampleRate, ///< [opt]: new sample rate for audio processing @see AudioEffect::setSampleRate
|
||||||
|
effSetBlockSize, ///< [value]: new maximum block size for audio processing @see AudioEffect::setBlockSize
|
||||||
|
effMainsChanged, ///< [value]: 0 means "turn off", 1 means "turn on" @see AudioEffect::suspend @see AudioEffect::resume
|
||||||
|
|
||||||
|
effEditGetRect, ///< [ptr]: #ERect** receiving pointer to editor size @see ERect @see AEffEditor::getRect
|
||||||
|
effEditOpen, ///< [ptr]: system dependent Window pointer, e.g. HWND on Windows @see AEffEditor::open
|
||||||
|
effEditClose, ///< no arguments @see AEffEditor::close
|
||||||
|
|
||||||
|
DECLARE_VST_DEPRECATED (effEditDraw), ///< \deprecated deprecated in VST 2.4
|
||||||
|
DECLARE_VST_DEPRECATED (effEditMouse), ///< \deprecated deprecated in VST 2.4
|
||||||
|
DECLARE_VST_DEPRECATED (effEditKey), ///< \deprecated deprecated in VST 2.4
|
||||||
|
|
||||||
|
effEditIdle, ///< no arguments @see AEffEditor::idle
|
||||||
|
|
||||||
|
DECLARE_VST_DEPRECATED (effEditTop), ///< \deprecated deprecated in VST 2.4
|
||||||
|
DECLARE_VST_DEPRECATED (effEditSleep), ///< \deprecated deprecated in VST 2.4
|
||||||
|
DECLARE_VST_DEPRECATED (effIdentify), ///< \deprecated deprecated in VST 2.4
|
||||||
|
|
||||||
|
effGetChunk, ///< [ptr]: void** for chunk data address [index]: 0 for bank, 1 for program @see AudioEffect::getChunk
|
||||||
|
effSetChunk, ///< [ptr]: chunk data [value]: byte size [index]: 0 for bank, 1 for program @see AudioEffect::setChunk
|
||||||
|
|
||||||
|
effNumOpcodes
|
||||||
|
};
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** Basic dispatcher Opcodes (Plug-in to Host) */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
enum AudioMasterOpcodes
|
||||||
|
{
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
audioMasterAutomate = 0, ///< [index]: parameter index [opt]: parameter value @see AudioEffect::setParameterAutomated
|
||||||
|
audioMasterVersion, ///< [return value]: Host VST version (for example 2400 for VST 2.4) @see AudioEffect::getMasterVersion
|
||||||
|
audioMasterCurrentId, ///< [return value]: current unique identifier on shell plug-in @see AudioEffect::getCurrentUniqueId
|
||||||
|
audioMasterIdle, ///< no arguments @see AudioEffect::masterIdle
|
||||||
|
DECLARE_VST_DEPRECATED (audioMasterPinConnected) ///< \deprecated deprecated in VST 2.4 r2
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
};
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** String length limits (in characters excl. 0 byte) */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
enum VstStringConstants
|
||||||
|
{
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
kVstMaxProgNameLen = 24, ///< used for #effGetProgramName, #effSetProgramName, #effGetProgramNameIndexed
|
||||||
|
kVstMaxParamStrLen = 8, ///< used for #effGetParamLabel, #effGetParamDisplay, #effGetParamName
|
||||||
|
kVstMaxVendorStrLen = 64, ///< used for #effGetVendorString, #audioMasterGetVendorString
|
||||||
|
kVstMaxProductStrLen = 64, ///< used for #effGetProductString, #audioMasterGetProductString
|
||||||
|
kVstMaxEffectNameLen = 32 ///< used for #effGetEffectName
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
};
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** String copy taking care of null terminator. */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
inline char* vst_strncpy (char* dst, const char* src, size_t maxLen)
|
||||||
|
{
|
||||||
|
char* result = strncpy (dst, src, maxLen);
|
||||||
|
dst[maxLen] = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** String concatenation taking care of null terminator. */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
inline char* vst_strncat (char* dst, const char* src, size_t maxLen)
|
||||||
|
{
|
||||||
|
char* result = strncat (dst, src, maxLen);
|
||||||
|
dst[maxLen] = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** Cast #VstIntPtr to pointer. */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
template <class T> inline T* FromVstPtr (VstIntPtr& arg)
|
||||||
|
{
|
||||||
|
T** address = (T**)&arg;
|
||||||
|
return *address;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** Cast pointer to #VstIntPtr. */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
template <class T> inline VstIntPtr ToVstPtr (T* ptr)
|
||||||
|
{
|
||||||
|
VstIntPtr* address = (VstIntPtr*)&ptr;
|
||||||
|
return *address;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** Structure used for #effEditGetRect. */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
struct ERect
|
||||||
|
{
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
VstInt16 top; ///< top coordinate
|
||||||
|
VstInt16 left; ///< left coordinate
|
||||||
|
VstInt16 bottom; ///< bottom coordinate
|
||||||
|
VstInt16 right; ///< right coordinate
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
};
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
#if TARGET_API_MAC_CARBON
|
||||||
|
#pragma options align=reset
|
||||||
|
#elif defined(WIN32) || defined(__FLAT__) || defined(__GNUC__)
|
||||||
|
#pragma pack(pop)
|
||||||
|
#elif defined __BORLANDC__
|
||||||
|
#pragma -a-
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __aeffect__
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,106 @@
|
|||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
// VST Plug-Ins SDK
|
||||||
|
// Version 2.4 $Date: 2006/02/09 11:05:51 $
|
||||||
|
//
|
||||||
|
// Category : VST 2.x Interfaces
|
||||||
|
// Filename : vstfxstore.h
|
||||||
|
// Created by : Steinberg Media Technologies
|
||||||
|
// Description : Definition of Program (fxp) and Bank (fxb) structures
|
||||||
|
//
|
||||||
|
// © 2006, Steinberg Media Technologies, All Rights Reserved
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifndef __vstfxstore__
|
||||||
|
#define __vstfxstore__
|
||||||
|
|
||||||
|
#ifndef __aeffect__
|
||||||
|
#include "aeffect.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** Root chunk identifier for Programs (fxp) and Banks (fxb). */
|
||||||
|
#define cMagic 'CcnK'
|
||||||
|
|
||||||
|
/** Regular Program (fxp) identifier. */
|
||||||
|
#define fMagic 'FxCk'
|
||||||
|
|
||||||
|
/** Regular Bank (fxb) identifier. */
|
||||||
|
#define bankMagic 'FxBk'
|
||||||
|
|
||||||
|
/** Program (fxp) identifier for opaque chunk data. */
|
||||||
|
#define chunkPresetMagic 'FPCh'
|
||||||
|
|
||||||
|
/** Bank (fxb) identifier for opaque chunk data. */
|
||||||
|
#define chunkBankMagic 'FBCh'
|
||||||
|
|
||||||
|
/*
|
||||||
|
Note: The C data structures below are for illustration only. You can not read/write them directly.
|
||||||
|
The byte order on disk of fxp and fxb files is Big Endian. You have to swap integer
|
||||||
|
and floating-point values on Little Endian platforms (Windows, MacIntel)!
|
||||||
|
*/
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** Program (fxp) structure. */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
struct fxProgram
|
||||||
|
{
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
VstInt32 chunkMagic; ///< 'CcnK'
|
||||||
|
VstInt32 byteSize; ///< size of this chunk, excl. magic + byteSize
|
||||||
|
|
||||||
|
VstInt32 fxMagic; ///< 'FxCk' (regular) or 'FPCh' (opaque chunk)
|
||||||
|
VstInt32 version; ///< format version (currently 1)
|
||||||
|
VstInt32 fxID; ///< fx unique ID
|
||||||
|
VstInt32 fxVersion; ///< fx version
|
||||||
|
|
||||||
|
VstInt32 numParams; ///< number of parameters
|
||||||
|
char prgName[28]; ///< program name (null-terminated ASCII string)
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
float params[1]; ///< variable sized array with parameter values
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
VstInt32 size; ///< size of program data
|
||||||
|
char chunk[1]; ///< variable sized array with opaque program data
|
||||||
|
} data; ///< program chunk data
|
||||||
|
} content; ///< program content depending on fxMagic
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
};
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
/** Bank (fxb) structure. */
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
struct fxBank
|
||||||
|
{
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
VstInt32 chunkMagic; ///< 'CcnK'
|
||||||
|
VstInt32 byteSize; ///< size of this chunk, excl. magic + byteSize
|
||||||
|
|
||||||
|
VstInt32 fxMagic; ///< 'FxBk' (regular) or 'FBCh' (opaque chunk)
|
||||||
|
VstInt32 version; ///< format version (1 or 2)
|
||||||
|
VstInt32 fxID; ///< fx unique ID
|
||||||
|
VstInt32 fxVersion; ///< fx version
|
||||||
|
|
||||||
|
VstInt32 numPrograms; ///< number of programs
|
||||||
|
|
||||||
|
#if VST_2_4_EXTENSIONS
|
||||||
|
VstInt32 currentProgram; ///< version 2: current program number
|
||||||
|
char future[124]; ///< reserved, should be zero
|
||||||
|
#else
|
||||||
|
char future[128]; ///< reserved, should be zero
|
||||||
|
#endif
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
fxProgram programs[1]; ///< variable number of programs
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
VstInt32 size; ///< size of bank data
|
||||||
|
char chunk[1]; ///< variable sized array with opaque bank data
|
||||||
|
} data; ///< bank chunk data
|
||||||
|
} content; ///< bank content depending on fxMagic
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __vstfxstore__
|
||||||
1
src/backend/src/engine/src/host/vst2/vst2_host.cpp
Normal file
1
src/backend/src/engine/src/host/vst2/vst2_host.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "vst2_host.h"
|
||||||
5
src/backend/src/engine/src/host/vst2/vst2_host.h
Normal file
5
src/backend/src/engine/src/host/vst2/vst2_host.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class vst2_host {
|
||||||
|
|
||||||
|
};
|
||||||
@@ -1,114 +1,184 @@
|
|||||||
//
|
#include <algorithm>
|
||||||
// Created by A on 2025/8/12.
|
#include <atomic>
|
||||||
//
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "zmq.h"
|
#include "ipc/ipc_node.h"
|
||||||
#include "daw_api.grpc.pb.h"
|
#include "ipc/shm_mgr.h"
|
||||||
|
#include "misc_type.h"
|
||||||
|
#include "plugin_manage/plugin_host_manager.h"
|
||||||
|
#include "rpc/engine_rpc.h"
|
||||||
|
|
||||||
class daw_api_project_service : public daw::api::ProjectService::Service {
|
boost::atomic<heartbeat_t>* engine_heartbeat = nullptr;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr float PI = 3.14159265359f;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AudioEngine {
|
||||||
public:
|
public:
|
||||||
grpc::Status NewProject(grpc::ServerContext* context,
|
static constexpr size_t AUDIO_BLOCK_SIZE = 512;
|
||||||
const daw::api::Empty* request,
|
static constexpr size_t SAMPLE_RATE = 44100;
|
||||||
daw::api::ProjectState* response) override {
|
static constexpr std::chrono::milliseconds PROCESSING_INTERVAL{10};
|
||||||
|
|
||||||
return grpc::Status::OK;
|
AudioEngine() : running_(false), processing_(false) {}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
running_ = true;
|
||||||
|
|
||||||
|
shm_mgr::get_instance().init(true);
|
||||||
|
|
||||||
|
(void)shm_mgr::msg_shm().destroy<boost::atomic<heartbeat_t>>("engine_heartbeat");
|
||||||
|
engine_heartbeat = shm_mgr::msg_shm().construct_with_name<boost::atomic<heartbeat_t>>(
|
||||||
|
"engine_heartbeat", std::chrono::high_resolution_clock::now().time_since_epoch().count());
|
||||||
|
|
||||||
|
heartbeat_thread_ = std::thread([this]() { heartbeat_loop(); });
|
||||||
|
audio_thread_ = std::thread([this]() { audio_processing_loop(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
grpc::Status LoadProject(grpc::ServerContext* context,
|
void stop() {
|
||||||
const daw::api::LoadProjectRequest* request,
|
running_ = false;
|
||||||
daw::api::ProjectState* response) override {
|
cv_.notify_all();
|
||||||
|
|
||||||
return grpc::Status::OK;
|
if (heartbeat_thread_.joinable()) {
|
||||||
|
heartbeat_thread_.join();
|
||||||
|
}
|
||||||
|
if (audio_thread_.joinable()) {
|
||||||
|
audio_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engine_heartbeat) {
|
||||||
|
*engine_heartbeat = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
grpc::Status SaveProject(grpc::ServerContext* context,
|
private:
|
||||||
const daw::api::SaveProjectRequest* request,
|
void heartbeat_loop() {
|
||||||
daw::api::StatusResponse* response) override {
|
while (running_) {
|
||||||
|
if (engine_heartbeat) {
|
||||||
return grpc::Status::OK;
|
*engine_heartbeat = std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void audio_processing_loop() {
|
||||||
|
engine_ipc_node node;
|
||||||
|
auto& host_manager = plugin_host_manager::get_instance();
|
||||||
|
auto host_instance = host_manager.load_plugin(R"(D:\Projects\Alicho\src\backend\src\vst2_host\test\4Front Piano x64.dll)");
|
||||||
|
if (!host_instance) {
|
||||||
|
std::cerr << "Failed to load plugin" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& audio_rb = host_instance->plugin_node_.get_audio_rb();
|
||||||
|
|
||||||
|
setup_audio_parameters(host_instance);
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
while (running_) {
|
||||||
|
auto start_time = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
node.process_rpc();
|
||||||
|
host_manager.tick();
|
||||||
|
|
||||||
|
if (!host_instance->is_process_running()) {
|
||||||
|
std::cerr << "Plugin process is no longer running." << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_audio_blocks(audio_rb);
|
||||||
|
|
||||||
|
auto end_time = std::chrono::steady_clock::now();
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
||||||
|
|
||||||
|
if (elapsed < PROCESSING_INTERVAL) {
|
||||||
|
auto sleep_time = PROCESSING_INTERVAL - elapsed;
|
||||||
|
cv_.wait_for(lock, sleep_time, [this] { return !running_; });
|
||||||
|
} else {
|
||||||
|
std::cerr << "Audio processing timeout: " << elapsed.count() << "ms" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup_audio_parameters(const std::shared_ptr<plugin_instance>& /*plugin*/) {
|
||||||
|
// TODO: propagate engine sample rate and block size to the plugin host via RPC.
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_audio_blocks(audio_ring_buffer<float>& rb) {
|
||||||
|
const auto processed_block = rb.get_processed_block();
|
||||||
|
|
||||||
|
if (!processed_block.data) {
|
||||||
|
if (!processing_) {
|
||||||
|
const auto pending_block = rb.acquire_pending_block(AUDIO_BLOCK_SIZE);
|
||||||
|
if (pending_block.data) {
|
||||||
|
generate_audio_data(pending_block);
|
||||||
|
rb.commit_pending_block(pending_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processing_ = true;
|
||||||
|
} else {
|
||||||
|
handle_processed_audio(processed_block);
|
||||||
|
rb.release_processed_block();
|
||||||
|
processing_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_audio_data(const audio_ring_buffer<float>::audio_block& block) {
|
||||||
|
static float phase = 0.0f;
|
||||||
|
const float frequency = 440.0f;
|
||||||
|
const float phase_increment = 2.0f * PI * frequency / static_cast<float>(SAMPLE_RATE);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < block.size; ++i) {
|
||||||
|
block.data[i] = std::sin(phase) * 0.1f;
|
||||||
|
phase += phase_increment;
|
||||||
|
if (phase > 2.0f * PI) {
|
||||||
|
phase -= 2.0f * PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_processed_audio(const audio_ring_buffer<float>::audio_block& block) {
|
||||||
|
float peak = 0.0f;
|
||||||
|
float rms = 0.0f;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < block.size; ++i) {
|
||||||
|
const float sample = std::abs(block.data[i]);
|
||||||
|
peak = std::max(peak, sample);
|
||||||
|
rms += sample * sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
rms = std::sqrt(rms / static_cast<float>(block.size));
|
||||||
|
|
||||||
|
static int counter = 0;
|
||||||
|
if (++counter % 100 == 0) {
|
||||||
|
std::cout << "Audio stats - Peak: " << peak << ", RMS: " << rms << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread heartbeat_thread_;
|
||||||
|
std::thread audio_thread_;
|
||||||
|
std::atomic<bool> running_;
|
||||||
|
std::atomic<bool> processing_;
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::condition_variable cv_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class daw_api_transport_service : public daw::api::TransportService::Service {
|
int main(int /*argc*/, char* /*argv*/[]) {
|
||||||
public:
|
AudioEngine engine;
|
||||||
grpc::Status Play(grpc::ServerContext* context,
|
|
||||||
const daw::api::Empty* request,
|
|
||||||
daw::api::StatusResponse* response) override {
|
|
||||||
|
|
||||||
return grpc::Status::OK;
|
engine.start();
|
||||||
}
|
|
||||||
|
|
||||||
grpc::Status Pause(grpc::ServerContext* context,
|
std::cout << "Audio engine started. Press Enter to stop..." << std::endl;
|
||||||
const daw::api::Empty* request,
|
std::cin.get();
|
||||||
daw::api::StatusResponse* response) override {
|
|
||||||
|
|
||||||
return grpc::Status::OK;
|
engine.stop();
|
||||||
}
|
|
||||||
|
|
||||||
grpc::Status Stop(grpc::ServerContext* context,
|
|
||||||
const daw::api::Empty* request,
|
|
||||||
daw::api::StatusResponse* response) override {
|
|
||||||
|
|
||||||
return grpc::Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
grpc::Status SetTempo(grpc::ServerContext* context,
|
|
||||||
const daw::api::SetTempoRequest* request,
|
|
||||||
daw::api::StatusResponse* response) override {
|
|
||||||
|
|
||||||
return grpc::Status::OK;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class daw_api_track_service : public daw::api::TrackService::Service {
|
|
||||||
public:
|
|
||||||
grpc::Status AddTrack(grpc::ServerContext* context,
|
|
||||||
const daw::api::AddTrackRequest* request,
|
|
||||||
daw::api::TrackInfo* response) override {
|
|
||||||
|
|
||||||
return grpc::Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
grpc::Status RemoveTrack(grpc::ServerContext* context,
|
|
||||||
const daw::api::TrackIdRequest* request,
|
|
||||||
daw::api::StatusResponse* response) override {
|
|
||||||
|
|
||||||
return grpc::Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
grpc::Status SetTrackVolume(grpc::ServerContext* context,
|
|
||||||
const daw::api::SetTrackVolumeRequest* request,
|
|
||||||
daw::api::StatusResponse* response) override {
|
|
||||||
|
|
||||||
return grpc::Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
grpc::Status SetTrackPan(grpc::ServerContext* context,
|
|
||||||
const daw::api::SetTrackPanRequest* request,
|
|
||||||
daw::api::StatusResponse* response) override {
|
|
||||||
|
|
||||||
return grpc::Status::OK;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class daw_api_plugin_service : public daw::api::PluginService::Service {
|
|
||||||
public:
|
|
||||||
grpc::Status LoadPlugin(grpc::ServerContext* context,
|
|
||||||
const daw::api::LoadPluginRequest* request,
|
|
||||||
daw::api::PluginInfo* response) override {
|
|
||||||
|
|
||||||
return grpc::Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
grpc::Status SetPluginParameter(grpc::ServerContext* context,
|
|
||||||
const daw::api::SetPluginParameterRequest* request,
|
|
||||||
daw::api::StatusResponse* response) override {
|
|
||||||
|
|
||||||
return grpc::Status::OK;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
103
src/backend/src/engine/src/plugin_manage/plugin_host_manager.cpp
Normal file
103
src/backend/src/engine/src/plugin_manage/plugin_host_manager.cpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#include "plugin_host_manager.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <boost/asio/steady_timer.hpp>
|
||||||
|
|
||||||
|
#include "plugin_instance.h"
|
||||||
|
|
||||||
|
plugin_host_manager::~plugin_host_manager() {
|
||||||
|
std::vector<uint32_t> ids_to_unload;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
for (const auto& id: plugin_instances_ | std::views::keys) {
|
||||||
|
ids_to_unload.push_back(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request graceful shutdown for all plugins first.
|
||||||
|
for (const auto& id: ids_to_unload) {
|
||||||
|
if (auto instance = get_plugin_instance(id)) {
|
||||||
|
instance->request_graceful_shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give plugins a moment to exit cleanly.
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
// Then force terminate any remaining processes.
|
||||||
|
for (const auto& id: ids_to_unload) {
|
||||||
|
unload_plugin(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<plugin_instance> plugin_host_manager::load_plugin(const std::filesystem::path& plugin_binary_path) {
|
||||||
|
auto plugin_id = get_next_plugin_id();
|
||||||
|
|
||||||
|
std::shared_ptr<plugin_instance> instance;
|
||||||
|
try {
|
||||||
|
instance = std::make_shared<plugin_instance>(io_context_, plugin_id, plugin_binary_path);
|
||||||
|
|
||||||
|
// Remove the instance from the manager once the host process exits.
|
||||||
|
instance->async_wait_for_exit([this, plugin_id](int exit_code) {
|
||||||
|
spdlog::info("Plugin {} exited with code {}", plugin_id, exit_code);
|
||||||
|
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
plugin_instances_.erase(plugin_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
plugin_instances_.emplace(plugin_id, instance);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
spdlog::error("无法加载插件 {}:{}", plugin_binary_path.string(), e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void plugin_host_manager::unload_plugin(uint32_t plugin_id) {
|
||||||
|
if (auto instance = get_plugin_instance(plugin_id)) {
|
||||||
|
// Try graceful shutdown first.
|
||||||
|
instance->request_graceful_shutdown();
|
||||||
|
|
||||||
|
// Allow a short grace period.
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
|
||||||
|
// Force terminate if the process is still running.
|
||||||
|
if (instance->is_process_running()) {
|
||||||
|
instance->kill_process(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
plugin_instances_.erase(plugin_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void plugin_host_manager::tick() {
|
||||||
|
// Service asynchronous operations.
|
||||||
|
io_context_.poll();
|
||||||
|
|
||||||
|
// Remove dead plugin instances.
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
for (auto it = plugin_instances_.begin(); it != plugin_instances_.end(); ) {
|
||||||
|
if (!it->second->is_process_running()) {
|
||||||
|
spdlog::info("Removing dead plugin instance {}", it->first);
|
||||||
|
it = plugin_instances_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<plugin_instance> plugin_host_manager::get_plugin_instance(uint32_t plugin_id) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
const auto it = plugin_instances_.find(plugin_id);
|
||||||
|
if (it != plugin_instances_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <ranges>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "lazy_singleton.h"
|
||||||
|
#include "plugin_instance.h"
|
||||||
|
|
||||||
|
class plugin_host_manager : public lazy_singleton<plugin_host_manager> {
|
||||||
|
public:
|
||||||
|
~plugin_host_manager();
|
||||||
|
|
||||||
|
std::shared_ptr<plugin_instance> load_plugin(const std::filesystem::path& plugin_binary_path);
|
||||||
|
void unload_plugin(uint32_t plugin_id);
|
||||||
|
void tick();
|
||||||
|
|
||||||
|
std::shared_ptr<plugin_instance> get_plugin_instance(uint32_t plugin_id);
|
||||||
|
|
||||||
|
template<typename Func>
|
||||||
|
void for_each_plugin_instance(Func&& func) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
for (const auto& instance: plugin_instances_ | std::views::values) {
|
||||||
|
func(instance.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
auto get_next_plugin_id() {
|
||||||
|
return plugin_id_.fetch_add(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::unordered_map<uint32_t, std::shared_ptr<plugin_instance>> plugin_instances_;
|
||||||
|
std::atomic_uint32_t plugin_id_ = 1;
|
||||||
|
boost::asio::io_context io_context_;
|
||||||
|
};
|
||||||
97
src/backend/src/engine/src/plugin_manage/plugin_instance.cpp
Normal file
97
src/backend/src/engine/src/plugin_manage/plugin_instance.cpp
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#include "plugin_instance.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
namespace bi = boost::interprocess;
|
||||||
|
namespace bp = boost::process_v2;
|
||||||
|
|
||||||
|
[[nodiscard]] static auto get_plugin_id_name(uint32_t in_id) {
|
||||||
|
return "plugin_" + std::to_string(in_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto execute_plugin_process(uint32_t id, boost::asio::io_context& ctx, const std::filesystem::path& plugin_path) {
|
||||||
|
if (!std::filesystem::exists(plugin_path)) {
|
||||||
|
throw std::runtime_error("插件路径不存在:" + plugin_path.string());
|
||||||
|
}
|
||||||
|
if (!plugin_path.has_extension()) {
|
||||||
|
throw std::runtime_error("插件路径必须带有扩展名:" + plugin_path.string());
|
||||||
|
}
|
||||||
|
// 启动沙箱进程
|
||||||
|
const auto& ext = plugin_path.extension();
|
||||||
|
const static std::unordered_map<std::string, std::string> supported_exts = {
|
||||||
|
{".so", "AlichoPluginHostVst2.exe"},
|
||||||
|
{".dll", "AlichoPluginHostVst2.exe"},
|
||||||
|
{".dylib", "AlichoPluginHostVst2.exe"},
|
||||||
|
{".vst2", "AlichoPluginHostVst2.exe"},
|
||||||
|
{".vst3", "AlichoPluginHostVst3.exe"},
|
||||||
|
{".clap", "AlichoPluginHostClap.exe"},
|
||||||
|
};
|
||||||
|
if (!supported_exts.contains(ext.string())) {
|
||||||
|
throw std::runtime_error("不支持的插件文件类型:" + ext.string());
|
||||||
|
}
|
||||||
|
const auto& host_executable = supported_exts.at(ext.string());
|
||||||
|
const auto& host_path = std::filesystem::current_path() / host_executable;
|
||||||
|
if (!std::filesystem::exists(host_path)) {
|
||||||
|
throw std::runtime_error("未找到主机可执行文件:" + host_path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用最新的Boost.Process v2 API
|
||||||
|
try {
|
||||||
|
return std::make_unique<bp::process>(ctx, host_path.string(), bp::process::args_t{get_plugin_id_name(id)});
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::error("Failed to create plugin process: {}", e.what());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin_instance::plugin_instance(boost::asio::io_context& ctx,
|
||||||
|
uint32_t in_id,
|
||||||
|
const std::filesystem::path& in_plugin_path) {
|
||||||
|
|
||||||
|
id = in_id;
|
||||||
|
|
||||||
|
// 创建共享内存段
|
||||||
|
// input_segment = std::make_unique<bi::managed_shared_memory>(bi::open_or_create, get_shm_in_name(id).c_str(), SHM_SIZE);
|
||||||
|
// output_segment = std::make_unique<bi::managed_shared_memory>(bi::open_or_create, get_shm_out_name(id).c_str(), SHM_SIZE);
|
||||||
|
|
||||||
|
// 获取锁无阻塞队列
|
||||||
|
// input_queue = input_segment->find_or_construct<lock_free_queue>("input_queue")();
|
||||||
|
// output_queue = output_segment->find_or_construct<lock_free_queue>("output_queue")();
|
||||||
|
|
||||||
|
plugin_node_.get_audio_rb().create_buffer(get_plugin_id_name(id), 10, 512); // 10块512采样点的缓冲区
|
||||||
|
|
||||||
|
process = execute_plugin_process(id, ctx, in_plugin_path);
|
||||||
|
|
||||||
|
if (!process || !is_process_running()) {
|
||||||
|
throw std::runtime_error("插件进程启动失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置进程退出回调
|
||||||
|
process->async_wait([this](boost::system::error_code ec, int exit_code) {
|
||||||
|
if (!ec) {
|
||||||
|
spdlog::info("Plugin process {} exited with code {}", id, exit_code);
|
||||||
|
// 在这里可以处理进程退出事件
|
||||||
|
is_processing_done = true;
|
||||||
|
is_registered = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
plugin_node_.open_queue(get_plugin_id_name(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin_instance::~plugin_instance() {
|
||||||
|
// 使用新的API进行优雅关闭
|
||||||
|
request_graceful_shutdown();
|
||||||
|
|
||||||
|
// 给予进程一定时间进行优雅关闭
|
||||||
|
boost::system::error_code ec;
|
||||||
|
if (process && process->is_open()) {
|
||||||
|
// 等待最多1秒
|
||||||
|
process->wait_for(std::chrono::seconds(1), ec);
|
||||||
|
if (ec) {
|
||||||
|
// 如果等待超时,强制终止
|
||||||
|
kill_process(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/backend/src/engine/src/plugin_manage/plugin_instance.h
Normal file
63
src/backend/src/engine/src/plugin_manage/plugin_instance.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/process/v2.hpp>
|
||||||
|
#include <boost/interprocess/managed_shared_memory.hpp>
|
||||||
|
|
||||||
|
#include "ipc/ipc_node.h"
|
||||||
|
#include "rpc/common.h"
|
||||||
|
|
||||||
|
namespace bp = boost::process_v2;
|
||||||
|
|
||||||
|
struct plugin_instance {
|
||||||
|
plugin_instance(boost::asio::io_context& ctx, uint32_t in_id, const std::filesystem::path& in_plugin_path);
|
||||||
|
~plugin_instance();
|
||||||
|
uint32_t id = 0;
|
||||||
|
|
||||||
|
// std::unique_ptr<boost::interprocess::managed_shared_memory> input_segment;
|
||||||
|
// std::unique_ptr<boost::interprocess::managed_shared_memory> output_segment;
|
||||||
|
// lock_free_queue* input_queue = nullptr; // AudioEngine -> Host
|
||||||
|
// lock_free_queue* output_queue = nullptr; // Host -> AudioEngine
|
||||||
|
|
||||||
|
// 状态标识
|
||||||
|
std::atomic_bool is_processing_done{ true }; // 插件是否处理完毕
|
||||||
|
std::atomic_bool is_registered{ false }; // RPC会话是否注册
|
||||||
|
|
||||||
|
auto is_process_running() const {
|
||||||
|
return process && process->is_open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void async_wait_for_exit(std::function<void(int)> callback) {
|
||||||
|
if (process && process->is_open()) {
|
||||||
|
process->async_wait([callback = std::move(callback)](boost::system::error_code ec, int exit_code) {
|
||||||
|
if (!ec) {
|
||||||
|
callback(exit_code);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void request_graceful_shutdown() {
|
||||||
|
if (process && process->is_open()) {
|
||||||
|
boost::system::error_code ec;
|
||||||
|
process->request_exit(ec);
|
||||||
|
if (!ec) {
|
||||||
|
// 优雅关闭请求已发送
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kill_process(bool wait) {
|
||||||
|
if (process && process->is_open()) {
|
||||||
|
boost::system::error_code ec;
|
||||||
|
process->terminate(ec);
|
||||||
|
if (wait && !ec) {
|
||||||
|
process->wait(ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin_ipc_remote_node plugin_node_;
|
||||||
|
private:
|
||||||
|
std::unique_ptr<bp::process> process; // 使用最新Boost.Process v2 API
|
||||||
|
};
|
||||||
5
src/backend/src/engine/src/rpc/engine_rpc.cpp
Normal file
5
src/backend/src/engine/src/rpc/engine_rpc.cpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#include "engine_rpc.h"
|
||||||
|
|
||||||
|
namespace engine_rpc {
|
||||||
|
RPC_REG(LOG, log_impl)
|
||||||
|
}
|
||||||
19
src/backend/src/engine/src/rpc/engine_rpc.h
Normal file
19
src/backend/src/engine/src/rpc/engine_rpc.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "rpc/rpc_type.h"
|
||||||
|
#include "rpc/rpc_manager.h"
|
||||||
|
|
||||||
|
namespace engine_rpc {
|
||||||
|
struct log_impl : log {
|
||||||
|
void process() {
|
||||||
|
const auto str = str_.get_string();
|
||||||
|
spdlog::log(level_, "[Sandbox] {}", str.c_str());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct test {
|
||||||
|
uint32_t id;
|
||||||
|
void process() {
|
||||||
|
spdlog::info("收到测试消息,ID: {}", id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -16,6 +16,11 @@ retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
|
|||||||
# @param ${SRC_FILES}: 构成该目标的所有源文件。
|
# @param ${SRC_FILES}: 构成该目标的所有源文件。
|
||||||
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
|
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
|
||||||
|
|
||||||
|
find_package(spdlog CONFIG REQUIRED)
|
||||||
|
find_package(gRPC CONFIG REQUIRED)
|
||||||
|
find_package(Protobuf CONFIG REQUIRED)
|
||||||
|
find_package(ZeroMQ REQUIRED)
|
||||||
|
|
||||||
# 将依赖库链接到我们的可执行文件目标。
|
# 将依赖库链接到我们的可执行文件目标。
|
||||||
# @param ${PROJECT_NAME}: 要链接的目标。
|
# @param ${PROJECT_NAME}: 要链接的目标。
|
||||||
# @param PRIVATE: 表示链接的依赖项仅对 ${PROJECT_NAME} 自身可见,
|
# @param PRIVATE: 表示链接的依赖项仅对 ${PROJECT_NAME} 自身可见,
|
||||||
@@ -25,5 +30,13 @@ add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
|
|||||||
# @param protobuf::libprotobuf: Protobuf 运行时库目标。
|
# @param protobuf::libprotobuf: Protobuf 运行时库目标。
|
||||||
# @param zmq: ZeroMQ 库目标。
|
# @param zmq: ZeroMQ 库目标。
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE config_target)
|
target_link_libraries(${PROJECT_NAME} PRIVATE config_target)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC spdlog::spdlog)
|
||||||
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
add_os_definitions(${PROJECT_NAME})
|
add_os_definitions(${PROJECT_NAME})
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC
|
||||||
|
Boost::circular_buffer
|
||||||
|
Boost::uuid
|
||||||
|
Boost::thread
|
||||||
|
Boost::interprocess
|
||||||
|
Boost::lockfree
|
||||||
|
)
|
||||||
|
|||||||
1
src/backend/src/misc/src/audio_ring_buffer.cpp
Normal file
1
src/backend/src/misc/src/audio_ring_buffer.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "audio_ring_buffer.h"
|
||||||
746
src/backend/src/misc/src/audio_ring_buffer.h
Normal file
746
src/backend/src/misc/src/audio_ring_buffer.h
Normal file
@@ -0,0 +1,746 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic> // 用于原子操作,实现无锁并发
|
||||||
|
#include <chrono> // 用于生成唯一名称(扩容时)
|
||||||
|
#include <memory> // 用于智能指针和内存管理
|
||||||
|
#include <stdexcept> // 用于抛出运行时异常
|
||||||
|
#include <string> // 用于字符串操作
|
||||||
|
#include <type_traits> // 用于编译时类型检查 (static_assert)
|
||||||
|
|
||||||
|
#include <boost/interprocess/allocators/allocator.hpp>
|
||||||
|
#include <boost/interprocess/managed_shared_memory.hpp>
|
||||||
|
|
||||||
|
#include "ipc/shm_mgr.h" // 假设这是共享内存管理的辅助类
|
||||||
|
|
||||||
|
namespace bip = boost::interprocess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 一个高性能、支持跨进程、可动态扩容的无锁音频环形缓冲区。
|
||||||
|
*
|
||||||
|
* 该类模板基于 Boost.Interprocess 共享内存实现,允许多个进程高效地共享音频数据流。
|
||||||
|
* 它采用了一个两阶段队列(pending 和 processed)来管理数据块的生命周期,
|
||||||
|
* 适用于生产者-消费者模型,例如一个进程写入音频数据,另一个进程进行处理。
|
||||||
|
*
|
||||||
|
*核心特性:
|
||||||
|
* - **跨进程共享:** 数据存储在共享内存中,可供多个进程同时访问。
|
||||||
|
* - **无锁设计:** 使用 std::atomic 实现关键位置的无锁读写,避免了锁竞争带来的性能开销。
|
||||||
|
* - **动态扩容:** 当缓冲区或内部队列空间不足时,会自动进行扩容,增强了系统的鲁棒性。
|
||||||
|
* - **两阶段提交流程:**
|
||||||
|
* 1. `acquire_pending_block` -> `commit_pending_block`: 生产者获取并填充数据。
|
||||||
|
* 2. `get_pending_block` -> `complete_pending_block`: 中间处理者(可选)获取待处理数据并标记为完成。
|
||||||
|
* 3. `get_processed_block` -> `release_processed_block`: 消费者获取已处理数据并释放空间。
|
||||||
|
* - **类型安全:** 使用模板和 static_assert 确保存储的类型是可平凡复制的(Trivially Copyable),
|
||||||
|
* 这对于在共享内存中直接进行内存操作至关重要。
|
||||||
|
*
|
||||||
|
* @tparam T 缓冲区中存储的音频数据样本类型(如 float, int16_t)。
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class audio_ring_buffer {
|
||||||
|
// 编译时检查,确保模板参数 T 是可以安全地在内存中直接复制的类型。
|
||||||
|
// 这是在共享内存中操作数据的基本要求。
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||||
|
|
||||||
|
public:
|
||||||
|
// --- 公共类型定义与结构体 (Public Types & Structs) ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 代表一个音频数据块的结构体。
|
||||||
|
*
|
||||||
|
* 作为与缓冲区交互的基本单位,包含了指向数据的指针、数据大小和时间戳。
|
||||||
|
*/
|
||||||
|
struct audio_block {
|
||||||
|
T* data; // 指向音频数据在共享内存中的起始位置
|
||||||
|
size_t size; // 数据块的大小(以元素 T 的数量计)
|
||||||
|
uint64_t timestamp; // 数据块关联的时间戳
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 默认构造函数
|
||||||
|
*/
|
||||||
|
audio_block() : data(nullptr), size(0), timestamp(0) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 带参数的构造函数
|
||||||
|
* @param ptr 指向数据的指针
|
||||||
|
* @param sz 数据块大小
|
||||||
|
* @param ts 时间戳
|
||||||
|
*/
|
||||||
|
audio_block(T* ptr, const size_t sz, const uint64_t ts = 0)
|
||||||
|
: data(ptr), size(sz), timestamp(ts) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 包含缓冲区当前状态的统计信息。
|
||||||
|
*/
|
||||||
|
struct buffer_stats {
|
||||||
|
size_t queue_capacity; // 内部队列的容量
|
||||||
|
size_t audio_buffer_size; // 音频数据缓冲区的总大小(元素数)
|
||||||
|
size_t pending_count; // 等待处理的数据块数量
|
||||||
|
size_t processed_count; // 已处理完成、等待消费的数据块数量
|
||||||
|
size_t available_space; // 音频缓冲区中可用的剩余空间(元素数)
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
// --- 私有辅助结构体 (Private Helper Structs) ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 存储在共享内存中的控制块。
|
||||||
|
*
|
||||||
|
* 包含所有环形缓冲区的状态信息和原子指针,用于协调跨进程读写。
|
||||||
|
*/
|
||||||
|
struct shared_control_block {
|
||||||
|
// 原子变量,用于无锁队列和缓冲区的位置跟踪
|
||||||
|
std::atomic<size_t> pending_write_pos{ 0 }; // "待处理"队列的写指针
|
||||||
|
std::atomic<size_t> pending_read_pos{ 0 }; // "待处理"队列的读指针
|
||||||
|
std::atomic<size_t> processed_write_pos{ 0 }; // "已处理"队列的写指针
|
||||||
|
std::atomic<size_t> processed_read_pos{ 0 }; // "已处理"队列的读指针
|
||||||
|
std::atomic<size_t> audio_write_pos{ 0 }; // 音频数据环形缓冲区的写指针
|
||||||
|
std::atomic<size_t> audio_read_pos{ 0 }; // 音频数据环形缓冲区的读指针
|
||||||
|
|
||||||
|
// 静态容量信息
|
||||||
|
size_t queue_capacity{}; // 队列容量 (pending 和 processed 队列大小相同)
|
||||||
|
size_t audio_buffer_size{}; // 音频数据缓冲区的总容量
|
||||||
|
|
||||||
|
// TODO: 这个参数似乎没有在当前逻辑中使用,可以考虑移除或实现相关功能。
|
||||||
|
size_t block_size{}; // 初始块大小
|
||||||
|
|
||||||
|
char initialized{}; // 初始化标志,用于确保共享内存只被初始化一次
|
||||||
|
|
||||||
|
shared_control_block() = default;
|
||||||
|
|
||||||
|
shared_control_block(size_t qcap, size_t abuf_size, size_t bsize)
|
||||||
|
: queue_capacity(qcap),
|
||||||
|
audio_buffer_size(abuf_size),
|
||||||
|
block_size(bsize),
|
||||||
|
initialized(1) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 内部队列的条目定义。
|
||||||
|
*
|
||||||
|
* 描述了一个已提交的音频块在主音频缓冲区中的位置和元数据。
|
||||||
|
*/
|
||||||
|
struct queue_entry {
|
||||||
|
size_t audio_offset; // 数据在 audio_buffer_ 中的偏移量
|
||||||
|
size_t size; // 数据块的大小
|
||||||
|
uint64_t timestamp; // 时间戳
|
||||||
|
|
||||||
|
queue_entry() = default;
|
||||||
|
|
||||||
|
queue_entry(size_t offset, size_t sz, uint64_t ts)
|
||||||
|
: audio_offset(offset), size(sz), timestamp(ts) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 私有成员变量 (Private Member Variables) ---
|
||||||
|
|
||||||
|
shm_block audio_shm_; // 共享内存管理器实例
|
||||||
|
std::string buffer_name_; // 缓冲区的唯一名称标识符
|
||||||
|
struct shm_data {
|
||||||
|
shm_obj_t<shared_control_block> control;
|
||||||
|
shm_obj_t<queue_entry> pending_queue;
|
||||||
|
shm_obj_t<queue_entry> processed_queue;
|
||||||
|
shm_obj_t<T> audio_buffer;
|
||||||
|
[[nodiscard]] bool is_valid() const {
|
||||||
|
return !control.is_null() && !pending_queue.is_null() && !processed_queue.is_null() && !audio_buffer.is_null();
|
||||||
|
}
|
||||||
|
} *data;
|
||||||
|
public:
|
||||||
|
// --- 构造、析构与生命周期管理 (Constructors, Destructor & Lifetime) ---
|
||||||
|
|
||||||
|
audio_ring_buffer() = default;
|
||||||
|
~audio_ring_buffer() = default;
|
||||||
|
|
||||||
|
// 移动构造函数:高效转移资源所有权
|
||||||
|
audio_ring_buffer(audio_ring_buffer&& other) noexcept : audio_shm_(std::move(other.audio_shm_)),
|
||||||
|
buffer_name_(std::move(other.buffer_name_)),
|
||||||
|
data(other.data) {
|
||||||
|
// 将源对象的指针置空,防止其析构时释放资源
|
||||||
|
other.shm_data.control = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动赋值运算符
|
||||||
|
audio_ring_buffer& operator=(audio_ring_buffer&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
audio_shm_ = other.audio_shm_;
|
||||||
|
buffer_name_ = std::move(other.buffer_name_);
|
||||||
|
data = other.data;
|
||||||
|
|
||||||
|
// 将源对象的指针置空
|
||||||
|
other.shm_data.control = {};
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用拷贝构造函数和拷贝赋值运算符,因为该类管理着独特的共享内存资源
|
||||||
|
audio_ring_buffer(const audio_ring_buffer&) = delete;
|
||||||
|
audio_ring_buffer& operator=(const audio_ring_buffer&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建一个新的共享音频缓冲区。
|
||||||
|
*
|
||||||
|
* 功能描述: 作为生产者或服务主控方,调用此函数来初始化共享内存区域和所有控制结构。
|
||||||
|
* @param in_name 缓冲区的唯一名称,用于其他进程连接。
|
||||||
|
* @param block_count 内部队列的初始容量(可以容纳多少个数据块)。
|
||||||
|
* @param block_size 单个数据块的典型大小(用于计算初始总缓冲区大小)。
|
||||||
|
* @returns 如果创建成功返回 true,否则返回 false。
|
||||||
|
* @example
|
||||||
|
* audio_ring_buffer<float> buffer;
|
||||||
|
* if (buffer.create_buffer("my_audio_stream", 100, 1024)) {
|
||||||
|
* // 创建成功
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
bool create_buffer(const std::string& in_name,
|
||||||
|
size_t block_count, // 块数量
|
||||||
|
size_t block_size) { // 单块大小(以 T 元素计)
|
||||||
|
// 将用户提供的直观参数(块数量、块大小)映射到内部实现所需的参数
|
||||||
|
const size_t queue_capacity = block_count;
|
||||||
|
const size_t audio_buffer_capacity = block_count * block_size;
|
||||||
|
|
||||||
|
audio_shm_ = shm_mgr::ab_shm();
|
||||||
|
buffer_name_ = in_name;
|
||||||
|
initialize_shared_structures(queue_capacity, audio_buffer_capacity, block_size);
|
||||||
|
return data->is_valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打开一个已存在的共享音频缓冲区。
|
||||||
|
*
|
||||||
|
* 功能描述: 作为消费者或后加入的进程,调用此函数来连接到由 create_buffer 创建的共享内存。
|
||||||
|
* @param in_name 要连接的缓冲区的唯一名称。
|
||||||
|
* @returns 如果连接成功返回 true,否则返回 false。
|
||||||
|
* @example
|
||||||
|
* audio_ring_buffer<float> consumer_buffer;
|
||||||
|
* if (consumer_buffer.open_buffer("my_audio_stream")) {
|
||||||
|
* // 连接成功
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
bool open_buffer(const std::string& in_name) {
|
||||||
|
audio_shm_ = shm_mgr::ab_shm();
|
||||||
|
buffer_name_ = in_name;
|
||||||
|
connect_to_existing_structures();
|
||||||
|
return data->is_valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 核心写入操作 (Core Write Operations) ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief (生产者) 获取一个可写入的音频块。
|
||||||
|
*
|
||||||
|
* 功能描述: 这是写入数据的第一步。请求一个指定大小的内存块用于写入。
|
||||||
|
* 设计思路:
|
||||||
|
* 1. 检查环形缓冲区是否有足够的连续空间。
|
||||||
|
* 2. 如果空间不足,会触发自动扩容机制 `try_expand_audio_buffer`。
|
||||||
|
* 3. 如果空间足够但写指针接近末尾(即需要环绕),则会尝试将写指针移到开头。
|
||||||
|
* 4. 返回一个 `audio_block` 结构,包含了可写入的内存地址和大小。
|
||||||
|
*
|
||||||
|
* @param required_size 需要写入的元素数量。
|
||||||
|
* @returns 一个有效的 audio_block。如果当前无法分配足够空间,则返回一个 data 为 nullptr 的无效 block。
|
||||||
|
* @example
|
||||||
|
* auto block = buffer.acquire_pending_block(512);
|
||||||
|
* if (block.data) {
|
||||||
|
* // 成功获取,可以向 block.data 写入 512 个 T 类型的元素
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
audio_block acquire_pending_block(size_t required_size) {
|
||||||
|
auto write_pos = data->control->audio_write_pos.load(std::memory_order_acquire);
|
||||||
|
auto read_pos = data->control->audio_read_pos.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
auto available = calculate_available_space(write_pos, read_pos);
|
||||||
|
|
||||||
|
// 如果当前可用空间不足,尝试扩展音频数据缓冲区
|
||||||
|
if (available < required_size) {
|
||||||
|
if (!try_expand_audio_buffer(required_size)) {
|
||||||
|
return audio_block(); // 扩展失败,返回无效块
|
||||||
|
}
|
||||||
|
// 扩容后,重新获取指针和可用空间
|
||||||
|
write_pos = data->control->audio_write_pos.load(std::memory_order_acquire);
|
||||||
|
read_pos = data->control->audio_read_pos.load(std::memory_order_acquire); // read_pos 也可能因数据整理而改变
|
||||||
|
available = calculate_available_space(write_pos, read_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查在当前写指针位置是否有足够的连续空间
|
||||||
|
if (write_pos + required_size > data->control->audio_buffer_size) {
|
||||||
|
// 如果尾部空间不足,检查头部是否有足够空间(即环绕)
|
||||||
|
if (read_pos >= required_size) {
|
||||||
|
// 可以安全地环绕到缓冲区开头
|
||||||
|
write_pos = 0;
|
||||||
|
// 注意:这里暂时不更新共享内存中的 write_pos,因为这只是一个预分配。
|
||||||
|
// 真正的更新发生在 commit_pending_block 中。
|
||||||
|
} else {
|
||||||
|
// 头部空间也不足,无法分配
|
||||||
|
return audio_block();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return audio_block(data->audio_buffer.to_local() + write_pos, required_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief (生产者) 提交一个已写入的音频块。
|
||||||
|
*
|
||||||
|
* 功能描述: 这是写入数据的第二步。在向 `acquire_pending_block` 获取的内存中写入数据后,
|
||||||
|
* 调用此函数将其提交到 "待处理" (pending) 队列中,以供后续处理。
|
||||||
|
* 设计思路:
|
||||||
|
* 1. 将数据块的元信息(偏移、大小、时间戳)存入 pending_queue_。
|
||||||
|
* 2. 原子地更新音频缓冲区的写指针 `audio_write_pos` 和 pending 队列的写指针 `pending_write_pos`。
|
||||||
|
* 3. 如果 pending 队列已满,会触发 `try_expand_queues` 进行扩容。
|
||||||
|
*
|
||||||
|
* @param block 从 `acquire_pending_block` 获取并已填充数据的 audio_block。
|
||||||
|
* @param timestamp 关联的时间戳。
|
||||||
|
* @returns 如果提交成功返回 true。
|
||||||
|
* @example
|
||||||
|
* auto block = buffer.acquire_pending_block(512);
|
||||||
|
* if (block.data) {
|
||||||
|
* fill_audio_data(block.data, 512);
|
||||||
|
* buffer.commit_pending_block(block, get_current_timestamp());
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
bool commit_pending_block(const audio_block& block, uint64_t timestamp = 0) {
|
||||||
|
auto queue_write = data->control->pending_write_pos.load(std::memory_order_acquire);
|
||||||
|
auto queue_read = data->control->pending_read_pos.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
// 检查 "待处理" 队列是否已满
|
||||||
|
if (is_queue_full(queue_write, queue_read)) {
|
||||||
|
if (!try_expand_queues()) {
|
||||||
|
return false; // 队列扩容失败
|
||||||
|
}
|
||||||
|
// 扩容后重新获取指针
|
||||||
|
queue_write = data->control->pending_write_pos.load(std::memory_order_acquire);
|
||||||
|
queue_read = data->control->pending_read_pos.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算数据在缓冲区中的偏移量
|
||||||
|
auto audio_offset = block.data - data->audio_buffer.to_local();
|
||||||
|
|
||||||
|
// 在队列中创建一个新条目
|
||||||
|
data->pending_queue[queue_write] = queue_entry(audio_offset, block.size, timestamp);
|
||||||
|
|
||||||
|
// 更新音频数据缓冲区的真实写指针
|
||||||
|
data->control->audio_write_pos.store(audio_offset + block.size, std::memory_order_release);
|
||||||
|
|
||||||
|
// 原子地推进 "待处理" 队列的写指针
|
||||||
|
auto next_write = (queue_write + 1) % control_->queue_capacity;
|
||||||
|
control_->pending_write_pos.store(next_write, std::memory_order_release);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 核心处理与读取操作 (Core Processing & Read Operations) ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief (处理器) 获取一个待处理的音频块。
|
||||||
|
*
|
||||||
|
* 功能描述: 从 "待处理" (pending) 队列头部获取一个数据块用于处理,但并不将其从队列中移除。
|
||||||
|
* @returns 如果队列不为空,返回一个有效的 audio_block;否则返回无效块。
|
||||||
|
* @example
|
||||||
|
* auto block_to_process = buffer.get_pending_block();
|
||||||
|
* if (block_to_process.data) {
|
||||||
|
* // 处理数据...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
audio_block get_pending_block() {
|
||||||
|
auto queue_read = control_->pending_read_pos.load(std::memory_order_acquire);
|
||||||
|
auto queue_write = control_->pending_write_pos.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
if (queue_read == queue_write) {
|
||||||
|
return audio_block(); // 队列为空
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue_entry& entry = pending_queue_[queue_read];
|
||||||
|
return audio_block(audio_buffer_ + entry.audio_offset, entry.size, entry.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief (处理器) 完成一个待处理音频块的加工。
|
||||||
|
*
|
||||||
|
* 功能描述: 将 `get_pending_block` 获取到的数据块从 "待处理" 队列移至 "已处理" (processed) 队列。
|
||||||
|
* 这标志着该数据块已处理完毕,可供最终消费者使用。
|
||||||
|
* 设计思路:
|
||||||
|
* 1. 从 pending 队列头部读取条目。
|
||||||
|
* 2. 将该条目复制到 processed 队列的尾部。
|
||||||
|
* 3. 原子地推进 pending 队列的读指针和 processed 队列的写指针。
|
||||||
|
* 4. 如果 processed 队列已满,会触发扩容。
|
||||||
|
*
|
||||||
|
* @returns 如果操作成功返回 true。
|
||||||
|
* @example
|
||||||
|
* auto block_to_process = buffer.get_pending_block();
|
||||||
|
* if (block_to_process.data) {
|
||||||
|
* process_audio(block_to_process.data, block_to_process.size);
|
||||||
|
* buffer.complete_pending_block();
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
bool complete_pending_block() {
|
||||||
|
auto pending_read = control_->pending_read_pos.load(std::memory_order_acquire);
|
||||||
|
auto pending_write = control_->pending_write_pos.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
if (pending_read == pending_write) {
|
||||||
|
return false; // "待处理" 队列为空
|
||||||
|
}
|
||||||
|
|
||||||
|
auto processed_write = control_->processed_write_pos.load(std::memory_order_acquire);
|
||||||
|
auto processed_read = control_->processed_read_pos.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
// 如果 "已处理" 队列满了,尝试扩展
|
||||||
|
if (is_queue_full(processed_write, processed_read)) {
|
||||||
|
if (!try_expand_queues()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 扩容后重新获取指针
|
||||||
|
processed_write = control_->processed_write_pos.load(std::memory_order_acquire);
|
||||||
|
processed_read = control_->processed_read_pos.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将条目从 pending 队列移动到 processed 队列
|
||||||
|
processed_queue_[processed_write] = pending_queue_[pending_read];
|
||||||
|
|
||||||
|
// 原子地更新两个队列的指针
|
||||||
|
auto next_pending_read = (pending_read + 1) % control_->queue_capacity;
|
||||||
|
auto next_processed_write = (processed_write + 1) % control_->queue_capacity;
|
||||||
|
control_->pending_read_pos.store(next_pending_read, std::memory_order_release);
|
||||||
|
control_->processed_write_pos.store(next_processed_write, std::memory_order_release);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief (消费者) 获取一个已处理的音频块。
|
||||||
|
*
|
||||||
|
* 功能描述: 从 "已处理" (processed) 队列头部获取一个数据块用于消费(如播放),但并不释放其空间。
|
||||||
|
* @returns 如果队列不为空,返回一个有效的 audio_block;否则返回无效块。
|
||||||
|
* @example
|
||||||
|
* auto block_to_play = buffer.get_processed_block();
|
||||||
|
* if (block_to_play.data) {
|
||||||
|
* // 播放音频...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
audio_block get_processed_block() {
|
||||||
|
auto queue_read = control_->processed_read_pos.load(std::memory_order_acquire);
|
||||||
|
auto queue_write = control_->processed_write_pos.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
if (queue_read == queue_write) {
|
||||||
|
return audio_block(); // 队列为空
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue_entry& entry = processed_queue_[queue_read];
|
||||||
|
return audio_block(audio_buffer_ + entry.audio_offset, entry.size, entry.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief (消费者) 释放一个已处理的音频块。
|
||||||
|
*
|
||||||
|
* 功能描述: 在消费完 `get_processed_block` 获取的数据后,调用此函数来释放其占用的空间。
|
||||||
|
* 设计思路:
|
||||||
|
* 1. 从 processed 队列头部移除条目(通过移动读指针)。
|
||||||
|
* 2. 将音频数据缓冲区的读指针 `audio_read_pos` 更新到被释放块的末尾。
|
||||||
|
* 这使得这部分空间可以被生产者重新使用。
|
||||||
|
*
|
||||||
|
* @returns 如果操作成功返回 true。
|
||||||
|
* @example
|
||||||
|
* auto block_to_play = buffer.get_processed_block();
|
||||||
|
* if (block_to_play.data) {
|
||||||
|
* play_audio(block_to_play.data, block_to_play.size);
|
||||||
|
* buffer.release_processed_block();
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
bool release_processed_block() {
|
||||||
|
auto queue_read = control_->processed_read_pos.load(std::memory_order_acquire);
|
||||||
|
auto queue_write = control_->processed_write_pos.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
if (queue_read == queue_write) {
|
||||||
|
return false; // "已处理" 队列为空
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue_entry& entry = processed_queue_[queue_read];
|
||||||
|
|
||||||
|
// 更新音频数据缓冲区的读指针,这步操作"释放"了环形缓冲区中的空间
|
||||||
|
auto expected_read_pos = entry.audio_offset + entry.size;
|
||||||
|
control_->audio_read_pos.store(expected_read_pos, std::memory_order_release);
|
||||||
|
|
||||||
|
// 原子地推进 "已处理" 队列的读指针
|
||||||
|
auto next_read = (queue_read + 1) % control_->queue_capacity;
|
||||||
|
control_->processed_read_pos.store(next_read, std::memory_order_release);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 状态查询与控制 (Status Query & Control) ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取待处理队列中的块数量。
|
||||||
|
* @returns 待处理的块数。
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t pending_count() const {
|
||||||
|
auto write = control_->pending_write_pos.load(std::memory_order_acquire);
|
||||||
|
auto read = control_->pending_read_pos.load(std::memory_order_acquire);
|
||||||
|
return write >= read ? write - read : control_->queue_capacity - read + write;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取已处理队列中的块数量。
|
||||||
|
* @returns 已处理的块数。
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t processed_count() const {
|
||||||
|
auto write = control_->processed_write_pos.load(std::memory_order_acquire);
|
||||||
|
auto read = control_->processed_read_pos.load(std::memory_order_acquire);
|
||||||
|
return write >= read ? write - read : control_->queue_capacity - read + write;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取音频缓冲区中可用的总空间。
|
||||||
|
* @returns 可用空间大小(以元素 T 计)。
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t available_audio_space() const {
|
||||||
|
auto write = control_->audio_write_pos.load(std::memory_order_acquire);
|
||||||
|
auto read = control_->audio_read_pos.load(std::memory_order_acquire);
|
||||||
|
return calculate_available_space(write, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重置整个缓冲区状态。
|
||||||
|
* @warning 这是一个危险操作,会清空所有队列和数据,可能导致数据丢失。
|
||||||
|
* 只应在确定没有其他进程正在使用缓冲区时调用。
|
||||||
|
*/
|
||||||
|
void reset() {
|
||||||
|
control_->pending_write_pos.store(0, std::memory_order_release);
|
||||||
|
control_->pending_read_pos.store(0, std::memory_order_release);
|
||||||
|
control_->processed_write_pos.store(0, std::memory_order_release);
|
||||||
|
control_->processed_read_pos.store(0, std::memory_order_release);
|
||||||
|
control_->audio_write_pos.store(0, std::memory_order_release);
|
||||||
|
control_->audio_read_pos.store(0, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓冲区的综合统计信息。
|
||||||
|
* @returns 一个 buffer_stats 结构体实例。
|
||||||
|
*/
|
||||||
|
[[nodiscard]] buffer_stats get_stats() const {
|
||||||
|
return { control_->queue_capacity,
|
||||||
|
control_->audio_buffer_size,
|
||||||
|
pending_count(),
|
||||||
|
processed_count(),
|
||||||
|
available_audio_space() };
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 兼容性接口 (Compatibility Interfaces) ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取底层的 Boost.Interprocess segment_manager。
|
||||||
|
* @returns 指向 segment_manager 的指针。
|
||||||
|
*/
|
||||||
|
[[nodiscard]] auto get_segment_manager() const {
|
||||||
|
return audio_shm_.get_segment_manager();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// --- 私有辅助函数 (Private Helper Functions) ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化所有共享内存中的数据结构。
|
||||||
|
*/
|
||||||
|
void initialize_shared_structures(size_t queue_capacity,
|
||||||
|
size_t audio_buffer_capacity,
|
||||||
|
size_t block_size) {
|
||||||
|
auto control = audio_shm_.construct<shared_control_block>(queue_capacity, audio_buffer_capacity, block_size);
|
||||||
|
auto pending_queue = audio_shm_.construct<queue_entry>();
|
||||||
|
auto processed_queue = audio_shm_.construct<queue_entry>();
|
||||||
|
auto audio_buffer = audio_shm_.construct<T>(audio_buffer_capacity);
|
||||||
|
|
||||||
|
data = audio_shm_.construct_with_name<shm_data>(buffer_name_);
|
||||||
|
data->control = control;
|
||||||
|
data->pending_queue = pending_queue;
|
||||||
|
data->processed_queue = processed_queue;
|
||||||
|
data->audio_buffer = audio_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 连接到已存在的共享内存数据结构。
|
||||||
|
*/
|
||||||
|
void connect_to_existing_structures() {
|
||||||
|
data = audio_shm_.find<shm_data>(buffer_name_);
|
||||||
|
if (!data) {
|
||||||
|
throw std::runtime_error("Failed to connect to existing shared memory structures");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 当音频缓冲区空间不足时尝试进行扩容。
|
||||||
|
*
|
||||||
|
* 设计思路:
|
||||||
|
* 1. 计算新容量:目标大小至少是当前大小的两倍,且必须能容纳本次请求的数据。
|
||||||
|
* 2. 创建新缓冲区:在共享内存中创建一个新的、更大的 T 类型数组。
|
||||||
|
* 3. 整理并拷贝数据:将旧缓冲区中的有效数据(可能存在环绕)线性地拷贝到新缓冲区的起始位置。
|
||||||
|
* 4. 更新状态:更新 control_ 块中的缓冲区大小、读写指针,并用新缓冲区替换旧的。
|
||||||
|
* 5. 释放旧缓冲区(由 shm_mgr 负责)。
|
||||||
|
*
|
||||||
|
* @param required_size 至少需要额外容纳的元素数量。
|
||||||
|
* @returns 扩容成功返回 true,失败(如共享内存不足)返回 false。
|
||||||
|
* @note 这是一个成本较高的操作,应尽量在初始化时分配合理大小以避免频繁扩容。
|
||||||
|
*/
|
||||||
|
// TODO: 这个函数逻辑较复杂,可以考虑拆分为更小的私有函数,
|
||||||
|
// 例如: `calculate_new_buffer_size`, `reorganize_and_copy_data`。
|
||||||
|
bool try_expand_audio_buffer(size_t required_size) {
|
||||||
|
const size_t cur_size = control_->audio_buffer_size;
|
||||||
|
const size_t write_pos = control_->audio_write_pos.load(std::memory_order_acquire);
|
||||||
|
const size_t read_pos = control_->audio_read_pos.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
// --- 1. 计算新容量和已用空间 ---
|
||||||
|
const size_t used =
|
||||||
|
write_pos >= read_pos ? write_pos - read_pos : cur_size - read_pos + write_pos;
|
||||||
|
size_t new_size = cur_size > 0 ? cur_size : 1;
|
||||||
|
// 扩容策略:每次翻倍,直到能容纳 (已用空间 + 新增需求 + 1个哨兵位)。
|
||||||
|
while (new_size - used - 1 < required_size) {
|
||||||
|
new_size <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. 创建新的共享内存缓冲区 ---
|
||||||
|
const std::string new_name = buffer_name_ + "_AudioBuffer_" + std::to_string(new_size);
|
||||||
|
T* new_buf = audio_shm_.find_or_construct<T>(new_name, new_size);
|
||||||
|
if (!new_buf) {
|
||||||
|
// log error: ("Audio buffer expand failed: not enough shared memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. 拷贝旧数据到新缓冲区(数据线性化)---
|
||||||
|
if (used > 0) {
|
||||||
|
if (write_pos > read_pos) { // 情况一:数据是连续的
|
||||||
|
std::memcpy(new_buf, audio_buffer_ + read_pos, used * sizeof(T));
|
||||||
|
} else { // 情况二:数据被环绕,分两段拷贝
|
||||||
|
// 拷贝 read_pos 到末尾的数据
|
||||||
|
const size_t tail_size = cur_size - read_pos;
|
||||||
|
std::memcpy(new_buf, audio_buffer_ + read_pos, tail_size * sizeof(T));
|
||||||
|
// 拷贝开头到 write_pos 的数据
|
||||||
|
std::memcpy(new_buf + tail_size, audio_buffer_, write_pos * sizeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 需要一种机制来销毁旧的 audio_buffer_ 对象以释放共享内存。
|
||||||
|
// 目前依赖于 shm_mgr 的实现,假设它能处理同名对象的替换。
|
||||||
|
// 例如:`audio_shm_.destroy<T>(buffer_name_ + "_AudioBuffer");`
|
||||||
|
|
||||||
|
// --- 4. 原子地更新所有指针和状态 ---
|
||||||
|
audio_buffer_ = new_buf;
|
||||||
|
control_->audio_buffer_size = new_size;
|
||||||
|
control_->audio_read_pos.store(0, std::memory_order_release); // 新缓冲区中,数据从 0 开始
|
||||||
|
control_->audio_write_pos.store(used, std::memory_order_release); // 写指针指向数据末尾
|
||||||
|
|
||||||
|
// log info: ("Audio buffer '{}' expanded: {} -> {} elements", buffer_name_, cur_size, new_size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 当内部队列满时尝试进行扩容。
|
||||||
|
*
|
||||||
|
* 设计思路:
|
||||||
|
* 1. 计算新容量(通常是当前的两倍)。
|
||||||
|
* 2. 创建新的、更大的 pending 和 processed 队列。
|
||||||
|
* 3. 将旧队列中的数据完整拷贝到新队列。
|
||||||
|
* 4. 更新内部指针指向新队列,并更新 control_ 块中的队列容量。
|
||||||
|
*
|
||||||
|
* @returns 扩容成功返回 true,失败返回 false。
|
||||||
|
*/
|
||||||
|
bool try_expand_queues() {
|
||||||
|
const size_t cur_capacity = control_->queue_capacity;
|
||||||
|
const size_t new_capacity = cur_capacity * 2;
|
||||||
|
const size_t pending_read = control_->pending_read_pos.load(std::memory_order_acquire);
|
||||||
|
const size_t pending_write = control_->pending_write_pos.load(std::memory_order_acquire);
|
||||||
|
const size_t processed_read = control_->processed_read_pos.load(std::memory_order_acquire);
|
||||||
|
const size_t processed_write =
|
||||||
|
control_->processed_write_pos.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
// --- 为队列创建临时唯一名称以避免冲突 ---
|
||||||
|
auto get_unique_name = [&](const std::string& base) {
|
||||||
|
return base + "_Expanded_" +
|
||||||
|
std::to_string(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch().count());
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 1. 创建新队列 ---
|
||||||
|
const std::string new_pending_name = get_unique_name(buffer_name_ + "_PendingQueue");
|
||||||
|
auto* new_pending_queue = audio_shm_.find_or_construct<queue_entry>(new_pending_name);
|
||||||
|
if (!new_pending_queue) return false;
|
||||||
|
|
||||||
|
const std::string new_processed_name = get_unique_name(buffer_name_ + "_ProcessedQueue");
|
||||||
|
auto* new_processed_queue = audio_shm_.find_or_construct<queue_entry>(new_processed_name);
|
||||||
|
if (!new_processed_queue) {
|
||||||
|
// 如果第二个队列创建失败,需要清理已创建的第一个队列
|
||||||
|
// audio_shm_.destroy<queue_entry>(new_pending_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. 拷贝数据并线性化 ---
|
||||||
|
// 拷贝 pending 队列
|
||||||
|
size_t count = 0;
|
||||||
|
for (size_t i = pending_read; i != pending_write; i = (i + 1) % cur_capacity) {
|
||||||
|
new_pending_queue[count++] = pending_queue_[i];
|
||||||
|
}
|
||||||
|
// 更新 pending 队列指针
|
||||||
|
control_->pending_read_pos.store(0, std::memory_order_release);
|
||||||
|
control_->pending_write_pos.store(count, std::memory_order_release);
|
||||||
|
|
||||||
|
// 拷贝 processed 队列
|
||||||
|
count = 0;
|
||||||
|
for (size_t i = processed_read; i != processed_write; i = (i + 1) % cur_capacity) {
|
||||||
|
new_processed_queue[count++] = processed_queue_[i];
|
||||||
|
}
|
||||||
|
// 更新 processed 队列指针
|
||||||
|
control_->processed_read_pos.store(0, std::memory_order_release);
|
||||||
|
control_->processed_write_pos.store(count, std::memory_order_release);
|
||||||
|
|
||||||
|
// --- 3. 更新内部指针和容量 ---
|
||||||
|
pending_queue_ = new_pending_queue;
|
||||||
|
processed_queue_ = new_processed_queue;
|
||||||
|
control_->queue_capacity = new_capacity;
|
||||||
|
|
||||||
|
// log info: ("Queue capacity expanded to {} for buffer: {}", new_capacity, buffer_name_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算环形缓冲区中的可用空间。
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t calculate_available_space(size_t write_pos,
|
||||||
|
size_t read_pos) const {
|
||||||
|
if (write_pos >= read_pos) {
|
||||||
|
// 写指针在前,可用空间是尾部剩余 + 头部已读 - 1(哨兵位)
|
||||||
|
return control_->audio_buffer_size - write_pos + read_pos - 1;
|
||||||
|
}
|
||||||
|
// 读指针在前(环绕),可用空间是两者之间的部分 - 1(哨兵位)
|
||||||
|
return read_pos - write_pos - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查环形队列是否已满。
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool is_queue_full(size_t write_pos, size_t read_pos) const {
|
||||||
|
return ((write_pos + 1) % control_->queue_capacity) == read_pos;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 工厂类 (Factory Class) ---
|
||||||
|
// 注意:以下工厂类提供了一种创建和连接缓冲区的替代方式。
|
||||||
|
// 随着 C++ 的发展,直接使用类本身的 create_buffer / open_buffer 成员函数
|
||||||
|
// 可能是更现代和清晰的用法。此工厂模式在此处保留,可能用于兼容旧代码或特定设计模式。
|
||||||
|
template <typename T>
|
||||||
|
class audio_ring_buffer_factory {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 创建一个新的 audio_ring_buffer 实例。
|
||||||
|
* @note 此函数已废弃,建议直接使用 `audio_ring_buffer<T>::create_buffer`。
|
||||||
|
*/
|
||||||
|
[[deprecated("Use audio_ring_buffer<T>::create_buffer instead.")]] static audio_ring_buffer<T>
|
||||||
|
create_new(const std::string& buffer_name, size_t block_count, size_t block_size) {
|
||||||
|
audio_ring_buffer<T> buffer;
|
||||||
|
buffer.create_buffer(buffer_name, block_count, block_size);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 连接到一个已存在的 audio_ring_buffer 实例。
|
||||||
|
* @note 此函数已废弃,建议直接使用 `audio_ring_buffer<T>::open_buffer`。
|
||||||
|
*/
|
||||||
|
[[deprecated("Use audio_ring_buffer<T>::open_buffer instead.")]] static audio_ring_buffer<T>
|
||||||
|
connect_existing(const std::string& buffer_name) {
|
||||||
|
audio_ring_buffer<T> buffer;
|
||||||
|
buffer.open_buffer(buffer_name);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
};
|
||||||
8
src/backend/src/misc/src/ipc/ipc_node.cpp
Normal file
8
src/backend/src/misc/src/ipc/ipc_node.cpp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* 文件: ipc_node.cpp
|
||||||
|
* 说明: ipc_node 相关成员实现,主要聚焦共享内存队列的创建/销毁与消息处理。
|
||||||
|
*/
|
||||||
|
#include "ipc_node.h"
|
||||||
|
|
||||||
|
#include <stdexcept> // std::runtime_error
|
||||||
|
#include <utility> // std::move
|
||||||
245
src/backend/src/misc/src/ipc/ipc_node.h
Normal file
245
src/backend/src/misc/src/ipc/ipc_node.h
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* 文件: ipc_node.h
|
||||||
|
* 功能: 提供进程/插件之间的 IPC(基于共享内存 + 无锁环形队列)节点封装,
|
||||||
|
* 并对外暴露简易的 RPC 调用与处理接口。
|
||||||
|
*
|
||||||
|
* 主要组件:
|
||||||
|
* 1. ipc_node —— 共享内存队列的最小包装,负责创建 / 打开 / 销毁队列,
|
||||||
|
* 并将 rpc_message_t 写入或读取出来。
|
||||||
|
* 2. engine_ipc_node —— 引擎侧封装,负责创建核心队列 "engine_queue" 并轮询处理。
|
||||||
|
* 3. plugin_ipc_node —— 插件侧封装,负责连接 "engine_queue" 并创建自身专用队列。
|
||||||
|
*
|
||||||
|
* ⚠️ 注意:
|
||||||
|
* • 所有队列均为单生产者单消费者 (SPSC),capacity 目前硬编码为 1024。
|
||||||
|
* • 调用顺序: 先 create_queue / open_queue,再进行 push/pop。
|
||||||
|
* • 队列在 owner 析构时会自动销毁,确保共享内存资源不泄漏。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// --- 头文件导入 (Headers) ----------------------------------------------------
|
||||||
|
#include <string>
|
||||||
|
#include <cstddef> // std::byte
|
||||||
|
#include <boost/interprocess/managed_shared_memory.hpp>
|
||||||
|
#include <boost/container/vector.hpp>
|
||||||
|
#include <boost/lockfree/spsc_queue.hpp>
|
||||||
|
#include <boost/lockfree/queue.hpp>
|
||||||
|
|
||||||
|
#include "audio_ring_buffer.h"
|
||||||
|
#include "ipc_queue.h"
|
||||||
|
#include "misc_type.h"
|
||||||
|
#include "shm_mgr.h"
|
||||||
|
#include "rpc/rpc_manager.h"
|
||||||
|
|
||||||
|
namespace bi = boost::interprocess;
|
||||||
|
namespace bc = boost::container;
|
||||||
|
|
||||||
|
// --- 基础节点 (ipc_node) -----------------------------------------------------
|
||||||
|
|
||||||
|
using ipc_msg_queue_t = ipc_queue<rpc_message_t>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class ipc_node
|
||||||
|
* @brief 对 boost::lockfree::spsc_queue 的一层轻量包装,
|
||||||
|
* 负责共享内存生命周期和基本 RPC push/pop。
|
||||||
|
*/
|
||||||
|
class ipc_node {
|
||||||
|
public:
|
||||||
|
friend class engine_ipc_node; // 引擎端友元,可直接访问 rpc_queue
|
||||||
|
friend class plugin_ipc_node; // 插件端友元
|
||||||
|
friend class plugin_ipc_remote_node; // 插件端远程调用节点友元
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 在共享内存中创建一条新的 SPSC 队列
|
||||||
|
* @param in_name 队列名字 (唯一 key)
|
||||||
|
* @throws std::runtime_error 创建失败时抛出
|
||||||
|
*/
|
||||||
|
void create_queue(const std::string& in_name) {
|
||||||
|
rpc_queue.create_queue(in_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打开 (attach) 已存在的 SPSC 队列
|
||||||
|
* @param in_name 队列名字
|
||||||
|
* @throws std::runtime_error 打开失败时抛出
|
||||||
|
*/
|
||||||
|
void open_queue(const std::string& in_name) {
|
||||||
|
rpc_queue.open_queue(in_name, std::chrono::seconds(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct msg_proxy {
|
||||||
|
rpc_message_t msg;
|
||||||
|
ipc_msg_queue_t* queue;
|
||||||
|
|
||||||
|
msg_proxy(ipc_msg_queue_t* in_queue) : queue(in_queue) {
|
||||||
|
auto message_id = T::rpc_id;
|
||||||
|
msg.message_id = message_id;
|
||||||
|
msg.make_data<T>();
|
||||||
|
}
|
||||||
|
void submit() const {
|
||||||
|
if (!queue->push(msg)) {
|
||||||
|
// 队列满了,丢弃消息并记录日志
|
||||||
|
spdlog::warn("IPC队列满,丢弃消息 ID: {}", static_cast<uint32_t>(msg.message_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T* operator->() const {
|
||||||
|
return msg.get_data<T>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @tparam Msg 任意带有静态成员 `static constexpr rpc::message_type rpc_id`
|
||||||
|
* @param in_msg 消息实例
|
||||||
|
* @brief 将消息序列化为 rpc_message_t 并推入队列,实现“火并忘”式 RPC。
|
||||||
|
*
|
||||||
|
* 实现细节:
|
||||||
|
* 1. 通过 reinterpret_cast + memcpy 直接拷贝二进制内存到 payload,
|
||||||
|
* 前提是 Msg 的二进制布局可跨进程使用(POD or 标准布局)。
|
||||||
|
* 2. 此处为 **拷贝** 行为,若 Msg 过大可能影响性能,
|
||||||
|
* 可考虑改用共享指针或零拷贝方案 (TODO)。
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* struct Ping { static constexpr auto rpc_id = rpc::message_type::Ping; };
|
||||||
|
* engine_node.call_rpc(Ping{});
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
template<typename Msg>
|
||||||
|
auto call_rpc()
|
||||||
|
{
|
||||||
|
msg_proxy<Msg> proxy{&rpc_queue};
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 轮询队列并分发全部消息
|
||||||
|
*
|
||||||
|
* 设计:采用 while(pop) 循环,直到当前队列为空。
|
||||||
|
*/
|
||||||
|
void process_rpc() {
|
||||||
|
rpc_message_t msg;
|
||||||
|
while (rpc_queue.pop(msg)) {
|
||||||
|
// 💡 此处零分支,直接交由全局分发器; 出队顺序即调用顺序
|
||||||
|
rpc_message_handler::get_instance().handle_message(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ipc_msg_queue_t rpc_queue; // 指向共享内存队列
|
||||||
|
|
||||||
|
ipc_node() = default; // 仅允许友元类/本类实例化
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 禁止拷贝/移动,防止多重析构
|
||||||
|
ipc_node(const ipc_node&) = delete;
|
||||||
|
|
||||||
|
ipc_node& operator=(const ipc_node&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 引擎端包装 (engine_ipc_node) -------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class engine_ipc_node
|
||||||
|
* @brief 引擎侧包装:
|
||||||
|
* • 创建 "engine_queue"
|
||||||
|
* • 提供 process_rpc() 供外部循环调用
|
||||||
|
*/
|
||||||
|
class engine_ipc_node {
|
||||||
|
public:
|
||||||
|
engine_ipc_node() { engine_queue.create_queue("engine_queue"); }
|
||||||
|
|
||||||
|
// 转调底层队列的消息处理
|
||||||
|
void process_rpc() { engine_queue.process_rpc(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ipc_node engine_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 插件端包装 (plugin_ipc_node) -------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class plugin_ipc_node
|
||||||
|
* @brief 插件侧包装:
|
||||||
|
* • 连接到引擎端 "engine_queue"
|
||||||
|
* • 创建自身专属队列 <in_queue_name>
|
||||||
|
*/
|
||||||
|
class plugin_ipc_node {
|
||||||
|
public:
|
||||||
|
explicit plugin_ipc_node(const std::string& in_queue_name): audio_rb() {
|
||||||
|
engine_queue.open_queue("engine_queue");
|
||||||
|
plugin_queue.create_queue(in_queue_name);
|
||||||
|
audio_rb.open_buffer(in_queue_name);
|
||||||
|
engine_heartbeat_ = shm_mgr::msg_shm().find<boost::atomic<heartbeat_t>>("engine_heartbeat");
|
||||||
|
if (!engine_heartbeat_) { throw std::runtime_error("无法找到 engine_running 标志"); }
|
||||||
|
plugin_heartbeat_ = shm_mgr::msg_shm().construct_with_name<boost::atomic<heartbeat_t>>(in_queue_name + "_heartbeat");
|
||||||
|
*plugin_heartbeat_ = std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- RPC 调用 -------------------
|
||||||
|
template<typename Msg>
|
||||||
|
auto call_rpc() { return engine_queue.call_rpc<Msg>(); }
|
||||||
|
|
||||||
|
// ------------------- RPC 处理 -------------------
|
||||||
|
void process_rpc() { plugin_queue.process_rpc(); }
|
||||||
|
|
||||||
|
[[nodiscard]] auto is_engine_running() const {
|
||||||
|
// 读取引擎心跳
|
||||||
|
const auto& val = engine_heartbeat_->load();
|
||||||
|
if (val == 0) {
|
||||||
|
return false; // 引擎已退出
|
||||||
|
}
|
||||||
|
// 心跳阈值 (2 秒)
|
||||||
|
static constexpr auto threshold = std::chrono::seconds(2);
|
||||||
|
// 心跳间隔超过阈值则视为引擎已崩溃
|
||||||
|
const auto duration = std::chrono::high_resolution_clock::now().time_since_epoch() - heartbeat_duration_t(val);
|
||||||
|
return duration < threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& get_audio_rb() { return audio_rb; }
|
||||||
|
private:
|
||||||
|
ipc_node engine_queue; // 指向引擎主队列(只写)
|
||||||
|
ipc_node plugin_queue; // 插件自队列(只读)
|
||||||
|
boost::atomic<heartbeat_t>* engine_heartbeat_;
|
||||||
|
boost::atomic<heartbeat_t>* plugin_heartbeat_;
|
||||||
|
audio_ring_buffer<float> audio_rb;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class plugin_ipc_remote_node
|
||||||
|
* @brief 在Audio Engine中的代理节点, 用于发送远程RPC调用到插件进程
|
||||||
|
*/
|
||||||
|
class plugin_ipc_remote_node {
|
||||||
|
public:
|
||||||
|
plugin_ipc_remote_node() : audio_rb() {}
|
||||||
|
|
||||||
|
template<typename Msg>
|
||||||
|
auto call_rpc() { return plugin_queue.call_rpc<Msg>(); }
|
||||||
|
|
||||||
|
void open_queue(const std::string& in_queue_name) {
|
||||||
|
plugin_queue.open_queue(in_queue_name);
|
||||||
|
plugin_heartbeat_ = shm_mgr::msg_shm().find<boost::atomic<heartbeat_t>>(in_queue_name + "_heartbeat");
|
||||||
|
audio_rb.open_buffer(in_queue_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto is_timeout(int32_t in_seconds) const {
|
||||||
|
if (!plugin_heartbeat_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto& val = plugin_heartbeat_->load();
|
||||||
|
if (val == 0) {
|
||||||
|
return true; // 插件已退出
|
||||||
|
}
|
||||||
|
// 心跳阈值 (5 秒)
|
||||||
|
const auto& threshold = std::chrono::seconds(in_seconds);
|
||||||
|
// 心跳间隔超过阈值则视为插件已超时
|
||||||
|
const auto duration = std::chrono::high_resolution_clock::now().time_since_epoch() - heartbeat_duration_t(val);
|
||||||
|
return duration >= threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto& get_audio_rb() { return audio_rb; }
|
||||||
|
private:
|
||||||
|
ipc_node plugin_queue{};
|
||||||
|
boost::atomic<heartbeat_t>* plugin_heartbeat_ = nullptr;
|
||||||
|
audio_ring_buffer<float> audio_rb;
|
||||||
|
};
|
||||||
1
src/backend/src/misc/src/ipc/ipc_queue.cpp
Normal file
1
src/backend/src/misc/src/ipc/ipc_queue.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "ipc_queue.h"
|
||||||
149
src/backend/src/misc/src/ipc/ipc_queue.h
Normal file
149
src/backend/src/misc/src/ipc/ipc_queue.h
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <boost/interprocess/containers/deque.hpp>
|
||||||
|
#include <boost/interprocess/sync/interprocess_mutex.hpp>
|
||||||
|
#include <boost/interprocess/sync/scoped_lock.hpp>
|
||||||
|
|
||||||
|
#include "shm_mgr.h"
|
||||||
|
#include "rpc/common.h"
|
||||||
|
|
||||||
|
|
||||||
|
// 线程安全的消息队列包装器
|
||||||
|
template<typename T>
|
||||||
|
class ipc_queue {
|
||||||
|
private:
|
||||||
|
using allocator_t = allocator_t<T>;
|
||||||
|
using ipc_msg_deque_t = boost::interprocess::deque<T, allocator_t>;
|
||||||
|
|
||||||
|
ipc_msg_deque_t* deque_;
|
||||||
|
boost::interprocess::interprocess_mutex* mutex_;
|
||||||
|
bool is_owner_; // 是否由本实例创建
|
||||||
|
std::string name_;
|
||||||
|
public:
|
||||||
|
// 在共享内存中构造
|
||||||
|
ipc_queue(): deque_(nullptr),
|
||||||
|
mutex_(nullptr),
|
||||||
|
is_owner_(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~ipc_queue() {
|
||||||
|
if (is_owner_) {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_queue(const std::string& in_name) {
|
||||||
|
name_ = in_name;
|
||||||
|
const auto& block = shm_mgr::msg_shm();
|
||||||
|
const auto& queue_name = get_queue_name();
|
||||||
|
const auto& mutex_name = get_mutex_name();
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
deque_ = block.construct_with_name<ipc_msg_deque_t>(queue_name, block.make_allocator<allocator_t>());
|
||||||
|
if (!deque_) {
|
||||||
|
throw std::runtime_error("无法创建IPC队列");
|
||||||
|
}
|
||||||
|
mutex_ = block.construct_with_name<boost::interprocess::interprocess_mutex>(mutex_name);
|
||||||
|
if (!mutex_) {
|
||||||
|
throw std::runtime_error("无法创建IPC队列锁");
|
||||||
|
}
|
||||||
|
is_owner_ = true;
|
||||||
|
}
|
||||||
|
void open_queue(const std::string& in_name, const std::optional<std::chrono::milliseconds> timeout = std::nullopt) {
|
||||||
|
name_ = in_name;
|
||||||
|
const auto& block = shm_mgr::msg_shm();
|
||||||
|
const auto& queue_name = get_queue_name();
|
||||||
|
const auto& mutex_name = get_mutex_name();
|
||||||
|
|
||||||
|
const auto& start_time = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
// 查找队列,支持超时重试
|
||||||
|
ipc_msg_deque_t* deque_ptr = nullptr;
|
||||||
|
do {
|
||||||
|
auto found_deque_ptr = block.find<ipc_msg_deque_t>(queue_name.c_str());
|
||||||
|
deque_ptr = found_deque_ptr;
|
||||||
|
|
||||||
|
if (deque_ptr) break;
|
||||||
|
|
||||||
|
if (timeout.has_value()) {
|
||||||
|
const auto& elapsed = std::chrono::steady_clock::now() - start_time;
|
||||||
|
if (elapsed >= timeout.value()) {
|
||||||
|
throw std::runtime_error("打开IPC队列超时");
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (timeout.has_value());
|
||||||
|
|
||||||
|
if (!deque_ptr) {
|
||||||
|
throw std::runtime_error("无法打开IPC队列");
|
||||||
|
}
|
||||||
|
deque_ = deque_ptr;
|
||||||
|
|
||||||
|
// 查找互斥锁,支持超时重试
|
||||||
|
boost::interprocess::interprocess_mutex* mutex_ptr = nullptr;
|
||||||
|
do {
|
||||||
|
auto found_mutex_ptr = block.find<boost::interprocess::interprocess_mutex>(mutex_name.c_str());
|
||||||
|
mutex_ptr = found_mutex_ptr;
|
||||||
|
|
||||||
|
if (mutex_ptr) break;
|
||||||
|
|
||||||
|
if (timeout.has_value()) {
|
||||||
|
auto elapsed = std::chrono::steady_clock::now() - start_time;
|
||||||
|
if (elapsed >= timeout.value()) {
|
||||||
|
throw std::runtime_error("打开IPC队列锁超时");
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (timeout.has_value());
|
||||||
|
|
||||||
|
if (!mutex_ptr) {
|
||||||
|
throw std::runtime_error("无法打开IPC队列锁");
|
||||||
|
}
|
||||||
|
mutex_ = mutex_ptr;
|
||||||
|
is_owner_ = false;
|
||||||
|
}
|
||||||
|
void destroy() {
|
||||||
|
shm_mgr::msg_shm().destroy<ipc_msg_deque_t>(get_queue_name());
|
||||||
|
shm_mgr::msg_shm().destroy<boost::interprocess::interprocess_mutex>(get_mutex_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool push(const T& msg) {
|
||||||
|
boost::interprocess::scoped_lock lock(*mutex_);
|
||||||
|
try {
|
||||||
|
deque_->push_back(msg);
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pop(T& msg) {
|
||||||
|
boost::interprocess::scoped_lock lock(*mutex_);
|
||||||
|
if (deque_->empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
msg = deque_->front();
|
||||||
|
deque_->pop_front();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto empty() const {
|
||||||
|
boost::interprocess::scoped_lock lock(*mutex_);
|
||||||
|
return deque_->empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto size() const {
|
||||||
|
boost::interprocess::scoped_lock lock(*mutex_);
|
||||||
|
return deque_->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_queue_name() const {
|
||||||
|
return name_ + "_queue";
|
||||||
|
}
|
||||||
|
[[nodiscard]] auto get_mutex_name() const {
|
||||||
|
return name_ + "_mutex";
|
||||||
|
}
|
||||||
|
};
|
||||||
1
src/backend/src/misc/src/ipc/plugin_sandbox_data.cpp
Normal file
1
src/backend/src/misc/src/ipc/plugin_sandbox_data.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "plugin_sandbox_data.h"
|
||||||
9
src/backend/src/misc/src/ipc/plugin_sandbox_data.h
Normal file
9
src/backend/src/misc/src/ipc/plugin_sandbox_data.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <boost/atomic.hpp>
|
||||||
|
|
||||||
|
class plugin_sandbox_data {
|
||||||
|
public:
|
||||||
|
boost::atomic<bool> ready{ false };
|
||||||
|
boost::atomic<uint64_t> frame_counter{0};
|
||||||
|
char name[256];
|
||||||
|
};
|
||||||
106
src/backend/src/misc/src/ipc/shm_audio_buffer.cpp
Normal file
106
src/backend/src/misc/src/ipc/shm_audio_buffer.cpp
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#include "shm_audio_buffer.h"
|
||||||
|
|
||||||
|
shm_audio_buffer::shm_audio_buffer(): buffers{ shm_mgr::ab_shm().make_allocator<channel_allocator_t>() } {
|
||||||
|
}
|
||||||
|
|
||||||
|
void shm_audio_buffer::channels(size_t in_ch) {
|
||||||
|
if (in_ch == 0) {
|
||||||
|
throw std::invalid_argument("Channel count must be greater than 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto alloc = shm_mgr::ab_shm().make_allocator<buffer_allocator_t>();
|
||||||
|
buffers.clear();
|
||||||
|
buffers.reserve(in_ch);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < in_ch; ++i) {
|
||||||
|
buffers.emplace_back(alloc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void shm_audio_buffer::block_size(size_t in_size) {
|
||||||
|
if (buffers.empty()) {
|
||||||
|
throw std::logic_error("Channels must be set before block size");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& buf : buffers) {
|
||||||
|
buf.resize(in_size, 0.0f); // 初始化为0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float* shm_audio_buffer::data(size_t ch) {
|
||||||
|
if (ch >= buffers.size()) {
|
||||||
|
throw std::out_of_range("Channel index out of range");
|
||||||
|
}
|
||||||
|
return buffers[ch].data();
|
||||||
|
}
|
||||||
|
|
||||||
|
const float* shm_audio_buffer::data(size_t ch) const {
|
||||||
|
if (ch >= buffers.size()) {
|
||||||
|
throw std::out_of_range("Channel index out of range");
|
||||||
|
}
|
||||||
|
return buffers[ch].data();
|
||||||
|
}
|
||||||
|
|
||||||
|
bc::vector<float*> shm_audio_buffer::headers() {
|
||||||
|
bc::vector<float*> ptrs;
|
||||||
|
ptrs.reserve(buffers.size());
|
||||||
|
|
||||||
|
for (auto& buf : buffers) {
|
||||||
|
ptrs.push_back(buf.data());
|
||||||
|
}
|
||||||
|
return ptrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
bc::vector<const float*> shm_audio_buffer::headers() const {
|
||||||
|
bc::vector<const float*> ptrs;
|
||||||
|
ptrs.reserve(buffers.size());
|
||||||
|
|
||||||
|
for (const auto& buf : buffers) {
|
||||||
|
ptrs.push_back(buf.data());
|
||||||
|
}
|
||||||
|
return ptrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
shm_audio_buffer::channel_vec_t& shm_audio_buffer::block(size_t ch) {
|
||||||
|
if (ch >= buffers.size()) {
|
||||||
|
throw std::out_of_range("Channel index out of range");
|
||||||
|
}
|
||||||
|
return buffers[ch];
|
||||||
|
}
|
||||||
|
|
||||||
|
const shm_audio_buffer::channel_vec_t& shm_audio_buffer::block(size_t ch) const {
|
||||||
|
if (ch >= buffers.size()) {
|
||||||
|
throw std::out_of_range("Channel index out of range");
|
||||||
|
}
|
||||||
|
return buffers[ch];
|
||||||
|
}
|
||||||
|
|
||||||
|
void shm_audio_buffer::clear() {
|
||||||
|
for (auto& buf : buffers) {
|
||||||
|
std::ranges::fill(buf, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void shm_audio_buffer::copy_from(size_t ch, const float* src, size_t samples) {
|
||||||
|
if (ch >= buffers.size()) {
|
||||||
|
throw std::out_of_range("Channel index out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samples > buffers[ch].size()) {
|
||||||
|
throw std::out_of_range("Sample count exceeds buffer size");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::copy_n(src, samples, buffers[ch].begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
void shm_audio_buffer::copy_to(size_t ch, float* dst, size_t samples) const {
|
||||||
|
if (ch >= buffers.size()) {
|
||||||
|
throw std::out_of_range("Channel index out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samples > buffers[ch].size()) {
|
||||||
|
throw std::out_of_range("Sample count exceeds buffer size");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::copy_n(buffers[ch].begin(), samples, dst);
|
||||||
|
}
|
||||||
71
src/backend/src/misc/src/ipc/shm_audio_buffer.h
Normal file
71
src/backend/src/misc/src/ipc/shm_audio_buffer.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// shm_audio_buffer.h
|
||||||
|
#pragma once
|
||||||
|
#include <boost/interprocess/managed_shared_memory.hpp>
|
||||||
|
#include <boost/interprocess/sync/interprocess_mutex.hpp>
|
||||||
|
#include <boost/interprocess/sync/scoped_lock.hpp>
|
||||||
|
#include <boost/container/vector.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include "shm_mgr.h"
|
||||||
|
|
||||||
|
namespace bi = boost::interprocess;
|
||||||
|
namespace bc = boost::container;
|
||||||
|
|
||||||
|
class shm_audio_buffer {
|
||||||
|
public:
|
||||||
|
using segment_manager = bi::managed_shared_memory::segment_manager;
|
||||||
|
using mutex_type = bi::interprocess_mutex;
|
||||||
|
using scoped_lock = bi::scoped_lock<mutex_type>;
|
||||||
|
|
||||||
|
using buffer_allocator_t = bi::allocator<float, segment_manager>;
|
||||||
|
using channel_vec_t = bc::vector<float, buffer_allocator_t>;
|
||||||
|
using channel_allocator_t = bi::allocator<channel_vec_t, segment_manager>;
|
||||||
|
using channel_container_t = bc::vector<channel_vec_t, channel_allocator_t>;
|
||||||
|
|
||||||
|
// 构造函数显式接收分配器
|
||||||
|
explicit shm_audio_buffer();
|
||||||
|
|
||||||
|
// 设置通道数
|
||||||
|
void channels(size_t in_ch);
|
||||||
|
|
||||||
|
// 获取通道数
|
||||||
|
[[nodiscard]] size_t channels() const {
|
||||||
|
return buffers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置块大小
|
||||||
|
void block_size(size_t in_size);
|
||||||
|
|
||||||
|
// 获取块大小
|
||||||
|
[[nodiscard]] size_t block_size() const {
|
||||||
|
if (buffers.empty()) return 0;
|
||||||
|
return buffers[0].size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定通道的数据指针
|
||||||
|
[[nodiscard]] float* data(size_t ch);
|
||||||
|
|
||||||
|
[[nodiscard]] const float* data(size_t ch) const;
|
||||||
|
|
||||||
|
// 获取所有通道的指针数组
|
||||||
|
[[nodiscard]] bc::vector<float*> headers();
|
||||||
|
|
||||||
|
[[nodiscard]] bc::vector<const float*> headers() const;
|
||||||
|
|
||||||
|
// 获取指定通道的向量引用
|
||||||
|
[[nodiscard]] channel_vec_t& block(size_t ch);
|
||||||
|
|
||||||
|
[[nodiscard]] const channel_vec_t& block(size_t ch) const;
|
||||||
|
|
||||||
|
// 清空所有缓冲区数据
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// 复制数据到缓冲区
|
||||||
|
void copy_from(size_t ch, const float* src, size_t samples);
|
||||||
|
|
||||||
|
// 复制数据从缓冲区
|
||||||
|
void copy_to(size_t ch, float* dst, size_t samples) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
channel_container_t buffers;
|
||||||
|
};
|
||||||
76
src/backend/src/misc/src/ipc/shm_mgr.cpp
Normal file
76
src/backend/src/misc/src/ipc/shm_mgr.cpp
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 文件: shm_manager.cpp
|
||||||
|
* 说明: shm_manager 成员函数实现。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "shm_mgr.h"
|
||||||
|
|
||||||
|
#include "shm_audio_buffer.h" // AudioBuffer 具体实现
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 共享内存整体分配
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 同时创建 / attach 三块 SHM (Message / AudioBuffer / String)
|
||||||
|
* @return true 创建成功, false 失败 (已打印日志)
|
||||||
|
*/
|
||||||
|
bool shm_mgr::allocate_shm() {
|
||||||
|
try {
|
||||||
|
message_segment = std::make_shared<expandable_shm_segment>(message_shm_name, message_shm_size, is_engine);
|
||||||
|
|
||||||
|
audio_buffer_segment = std::make_shared<expandable_shm_segment>(audio_buffer_shm_name,
|
||||||
|
audio_buffer_shm_size,
|
||||||
|
is_engine);
|
||||||
|
|
||||||
|
str_segment = std::make_shared<expandable_shm_segment>(string_shm_name, str_shm_size, is_engine);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const bi::interprocess_exception& e) {
|
||||||
|
spdlog::error("无法创建共享内存:{}", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto shm_ptr_t::to_local() const -> void* {
|
||||||
|
void* ptr;
|
||||||
|
switch (type) {
|
||||||
|
case shm_block_type::message:
|
||||||
|
ptr = shm_mgr::msg_shm().get_address_from_handle(handle);
|
||||||
|
break;
|
||||||
|
case shm_block_type::audio_buffer:
|
||||||
|
ptr = shm_mgr::ab_shm().get_address_from_handle(handle);
|
||||||
|
break;
|
||||||
|
case shm_block_type::string:
|
||||||
|
ptr = shm_mgr::str_shm().get_address_from_handle(handle);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("未知的 shm_block_type");
|
||||||
|
}
|
||||||
|
if (!ptr) {
|
||||||
|
throw std::runtime_error("无效的共享内存句柄: " + std::to_string(handle));
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto shm_ptr_t::from_local(shm_block_type in_type, void* in_ptr) -> shm_ptr_t {
|
||||||
|
int64_t h = 0;
|
||||||
|
switch (in_type) {
|
||||||
|
case shm_block_type::message:
|
||||||
|
h = shm_mgr::msg_shm().get_handle_from_address(in_ptr);
|
||||||
|
break;
|
||||||
|
case shm_block_type::audio_buffer:
|
||||||
|
h = shm_mgr::ab_shm().get_handle_from_address(in_ptr);
|
||||||
|
break;
|
||||||
|
case shm_block_type::string:
|
||||||
|
h = shm_mgr::str_shm().get_handle_from_address(in_ptr);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("未知的 shm_block_type");
|
||||||
|
}
|
||||||
|
if (h == 0) {
|
||||||
|
throw std::runtime_error("无效的共享内存指针");
|
||||||
|
}
|
||||||
|
return shm_ptr_t(in_type, h);
|
||||||
|
}
|
||||||
472
src/backend/src/misc/src/ipc/shm_mgr.h
Normal file
472
src/backend/src/misc/src/ipc/shm_mgr.h
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
/*
|
||||||
|
* 文件: shm_manager.h
|
||||||
|
* 职责: 统一管理项目中所有共享内存段 (Message / AudioBuffer / String),
|
||||||
|
* 并为外部提供 "构造 / 查找 / 销毁" 等高层工具函数,隐藏 Boost-Interprocess
|
||||||
|
* 的细节,做到"拿来即用"。
|
||||||
|
*
|
||||||
|
* 关键点:
|
||||||
|
* • 采用 lazy_singleton 保证进程内唯一实例;
|
||||||
|
* • 引擎端 (is_engine = true) 负责创建并在退出时移除 SHM;
|
||||||
|
* • 插件端 (is_engine = false) 仅负责 attach,无权销毁;
|
||||||
|
* • 把 segment_manager 暴露给模板工具 (msg_mgr / ab_mgr),
|
||||||
|
* 方便外界使用自定义分配器;
|
||||||
|
* • 自动扩容策略:内存不足时透明创建新块,对外接口保持不变。
|
||||||
|
*
|
||||||
|
* ⚠️ 注意:
|
||||||
|
* • Boost SHM 创建失败时会抛出 interprocess_exception,需捕获。
|
||||||
|
* • 所有模板接口均 inline + static,可直接在头文件使用。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// --- 头文件导入 (Headers) ----------------------------------------------------
|
||||||
|
#include <memory> // std::shared_ptr
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
#include <boost/atomic/atomic.hpp>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <boost/interprocess/managed_shared_memory.hpp>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "lazy_singleton.h"
|
||||||
|
#include "rpc/common.h" // allocator_t / shm_string 等依赖
|
||||||
|
|
||||||
|
// 前向声明 (避免循环依赖)
|
||||||
|
class shm_audio_buffer;
|
||||||
|
|
||||||
|
// 命名空间别名
|
||||||
|
namespace bi = boost::interprocess;
|
||||||
|
|
||||||
|
using shm_block_ptr = std::shared_ptr<bi::managed_shared_memory>;
|
||||||
|
|
||||||
|
// 内部扩容管理器 - 对外完全透明
|
||||||
|
class expandable_shm_segment {
|
||||||
|
public:
|
||||||
|
explicit expandable_shm_segment(std::string base_name,
|
||||||
|
const std::size_t initial_size,
|
||||||
|
const bool is_engine) : base_name_(std::move(base_name)),
|
||||||
|
block_size_(initial_size),
|
||||||
|
is_engine_(is_engine),
|
||||||
|
next_block_id_(0) { create_initial_block(); }
|
||||||
|
|
||||||
|
~expandable_shm_segment() { if (is_engine_) { cleanup_all_blocks(); } }
|
||||||
|
|
||||||
|
// 分配内存 - 自动扩容
|
||||||
|
void* allocate(std::size_t size) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
// 尝试从现有块分配
|
||||||
|
for (auto& block: blocks_) {
|
||||||
|
try { return block->allocate(size); }
|
||||||
|
catch (const bi::bad_alloc&) {
|
||||||
|
continue; // 这个块满了,尝试下一个
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有块都满了,创建新块
|
||||||
|
if (is_engine_ && create_new_block()) {
|
||||||
|
try { return blocks_.back()->allocate(size); }
|
||||||
|
catch (const bi::bad_alloc&) {
|
||||||
|
// 新块也分配失败,返回 nullptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(void* ptr) {
|
||||||
|
if (!ptr)
|
||||||
|
return;
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
for (auto& block: blocks_) {
|
||||||
|
try {
|
||||||
|
block->deallocate(ptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
continue; // 指针不属于这个块
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T* construct(const char* name) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
|
||||||
|
// 尝试从现有块构造
|
||||||
|
for (auto& block: blocks_) {
|
||||||
|
try { return block->construct<T>(name)(); }
|
||||||
|
catch (const bi::bad_alloc&) { continue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有块都满了,创建新块
|
||||||
|
if (is_engine_ && create_new_block()) {
|
||||||
|
try { return blocks_.back()->construct<T>(name)(); }
|
||||||
|
catch (const bi::bad_alloc&) {
|
||||||
|
// 新块也分配失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
T* construct(const char* name, Args&&... args) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
|
||||||
|
// 尝试从现有块构造
|
||||||
|
for (const auto& block: blocks_) {
|
||||||
|
try { return block->construct<T>(name)(std::forward<Args>(args)...); }
|
||||||
|
catch (const bi::bad_alloc&) { continue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有块都满了,创建新块
|
||||||
|
if (is_engine_ && create_new_block()) {
|
||||||
|
try { return blocks_.back()->construct<T>(name)(std::forward<Args>(args)...); }
|
||||||
|
catch (const bi::bad_alloc&) {
|
||||||
|
// 新块也分配失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T* find_or_construct(const char* name) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
|
||||||
|
// 首先在所有块中查找
|
||||||
|
for (const auto& block: blocks_) {
|
||||||
|
const auto& result = block->find<T>(name);
|
||||||
|
if (result.first) { return result.first; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未找到,尝试构造
|
||||||
|
for (const auto& block: blocks_) {
|
||||||
|
try { return block->find_or_construct<T>(name)(); }
|
||||||
|
catch (const bi::bad_alloc&) { continue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有块都满了,创建新块
|
||||||
|
if (is_engine_ && create_new_block()) {
|
||||||
|
try { return blocks_.back()->find_or_construct<T>(name)(); }
|
||||||
|
catch (const bi::bad_alloc&) {
|
||||||
|
// 新块也分配失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
T* find_or_construct(const char* name, Args&&... args) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
|
||||||
|
// 首先在所有块中查找
|
||||||
|
for (const auto& block: blocks_) {
|
||||||
|
const auto& result = block->find<T>(name);
|
||||||
|
if (result.first) { return result.first; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未找到,尝试构造
|
||||||
|
for (const auto& block: blocks_) {
|
||||||
|
try { return block->find_or_construct<T>(name)(std::forward<Args>(args)...); }
|
||||||
|
catch (const bi::bad_alloc&) { continue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有块都满了,创建新块
|
||||||
|
if (is_engine_ && create_new_block()) {
|
||||||
|
try { return blocks_.back()->find_or_construct<T>(name)(std::forward<Args>(args)...); }
|
||||||
|
catch (const bi::bad_alloc&) {
|
||||||
|
// 新块也分配失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T* find(const char* name) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
for (const auto& block: blocks_) {
|
||||||
|
const auto& result = block->find<T>(name);
|
||||||
|
if (result.first) { return result.first; }
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool destroy(const char* name) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
for (auto& block: blocks_) { if (block->destroy<T>(name)) { return true; } }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bi::managed_shared_memory::handle_t get_handle_from_address(const void* ptr) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
for (auto& block: blocks_) {
|
||||||
|
try { return block->get_handle_from_address(ptr); }
|
||||||
|
catch (...) { continue; }
|
||||||
|
}
|
||||||
|
return 0; // 无效句柄
|
||||||
|
}
|
||||||
|
|
||||||
|
void* get_address_from_handle(bi::managed_shared_memory::handle_t handle) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
for (auto& block: blocks_) {
|
||||||
|
try {
|
||||||
|
auto ptr = block->get_address_from_handle(handle);
|
||||||
|
if (ptr)
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
catch (...) { continue; }
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回第一个块的 segment_manager (保持兼容性)
|
||||||
|
auto get_segment_manager() {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
return blocks_.empty() ? nullptr : blocks_[0]->get_segment_manager();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void create_initial_block() {
|
||||||
|
try {
|
||||||
|
const auto& shm_ptr = create_shm_block(base_name_, block_size_);
|
||||||
|
blocks_.push_back(shm_ptr);
|
||||||
|
spdlog::debug("创建初始内存块: {} ({}MB)", base_name_, block_size_ / (1024 * 1024));
|
||||||
|
}
|
||||||
|
catch (const bi::interprocess_exception& e) { spdlog::error("创建初始内存块失败 {}: {}", base_name_, e.what()); }
|
||||||
|
}
|
||||||
|
|
||||||
|
bool create_new_block() {
|
||||||
|
if (!is_engine_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auto block_name = base_name_ + "_" + std::to_string(++next_block_id_);
|
||||||
|
const auto& shm_ptr = create_shm_block(block_name, block_size_);
|
||||||
|
blocks_.push_back(shm_ptr);
|
||||||
|
|
||||||
|
spdlog::info("内存自动扩容: {} ({}MB), 总块数: {}", block_name, block_size_ / (1024 * 1024), blocks_.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const bi::interprocess_exception& e) {
|
||||||
|
spdlog::error("扩容失败 {}: {}", base_name_, e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shm_block_ptr create_shm_block(const std::string& name, std::size_t size) {
|
||||||
|
if (is_engine_) {
|
||||||
|
bi::shared_memory_object::remove(name.c_str());
|
||||||
|
spdlog::debug("创建共享内存块: {} ({}MB)", name, size / (1024 * 1024));
|
||||||
|
return std::make_shared<bi::managed_shared_memory>(bi::create_only, name.c_str(), size);
|
||||||
|
}
|
||||||
|
return std::make_shared<bi::managed_shared_memory>(bi::open_only, name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup_all_blocks() {
|
||||||
|
for (std::size_t i = 0; i < blocks_.size(); ++i) {
|
||||||
|
const auto block_name = (i == 0) ? base_name_ : base_name_ + "_" + std::to_string(i);
|
||||||
|
bi::shared_memory_object::remove(block_name.c_str());
|
||||||
|
spdlog::debug("移除共享内存块: {}", block_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string base_name_;
|
||||||
|
std::size_t block_size_;
|
||||||
|
bool is_engine_;
|
||||||
|
std::atomic<std::size_t> next_block_id_;
|
||||||
|
std::vector<shm_block_ptr> blocks_;
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class shm_block_type {
|
||||||
|
invalid = 0,
|
||||||
|
message,
|
||||||
|
audio_buffer,
|
||||||
|
string
|
||||||
|
};
|
||||||
|
|
||||||
|
// 共享内存指针 - 可跨进程传递
|
||||||
|
struct shm_ptr_t {
|
||||||
|
shm_block_type type;
|
||||||
|
int64_t handle;
|
||||||
|
|
||||||
|
shm_ptr_t(shm_block_type in_type, int64_t in_handle) : type(in_type), handle(in_handle) {}
|
||||||
|
|
||||||
|
[[nodiscard]] auto to_local() const -> void*;
|
||||||
|
template<typename T>
|
||||||
|
[[nodiscard]] auto to_local() const -> T* {
|
||||||
|
return static_cast<T*>(to_local());
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static auto from_local(shm_block_type in_type, void* in_ptr) -> shm_ptr_t;
|
||||||
|
[[nodiscard]] static auto null() -> shm_ptr_t { return shm_ptr_t{ shm_block_type::invalid, 0 }; }
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_null() const { return type == shm_block_type::invalid || handle == 0; }
|
||||||
|
explicit operator bool() const { return !is_null(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct shm_obj_t {
|
||||||
|
shm_ptr_t ptr;
|
||||||
|
|
||||||
|
[[nodiscard]] auto is_null() const { return ptr.is_null(); }
|
||||||
|
[[nodiscard]] auto to_local() const { return ptr.to_local<T>(); }
|
||||||
|
|
||||||
|
auto operator ->() const { return ptr.to_local<T>(); }
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return !ptr.is_null();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 包装器 - 保持原有接口完全不变
|
||||||
|
struct shm_block {
|
||||||
|
shm_block() = default;
|
||||||
|
explicit shm_block(shm_block_type in_type, std::shared_ptr<expandable_shm_segment> segment): type(in_type), segment_(std::move(segment)) {
|
||||||
|
}
|
||||||
|
shm_block(const shm_block&) = default;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto alloc() const { return segment_->allocate(sizeof(T)); }
|
||||||
|
|
||||||
|
void dealloc(void* ptr) const { segment_->deallocate(ptr); }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
[[nodiscard]] auto construct() const {
|
||||||
|
auto ptr = alloc<T>();
|
||||||
|
if (!ptr)
|
||||||
|
return shm_ptr_t::null();
|
||||||
|
new(ptr) T();
|
||||||
|
return shm_ptr_t::from_local(type, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
[[nodiscard]] auto construct(Args&&... args) const {
|
||||||
|
auto ptr = alloc<T>();
|
||||||
|
if (!ptr)
|
||||||
|
return shm_ptr_t::null();
|
||||||
|
new(ptr) T(std::forward<Args>(args)...);
|
||||||
|
return shm_ptr_t::from_local(type, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
[[nodiscard]] auto construct_with_name(const std::string& name) const {
|
||||||
|
return segment_->construct<T>(name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
[[nodiscard]] auto construct_with_name(const std::string& name, Args&&... args) const {
|
||||||
|
return segment_->construct<T>(name.c_str(), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
[[nodiscard]] auto find_or_construct(const std::string& name) const {
|
||||||
|
return segment_->find_or_construct<T>(name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
[[nodiscard]] auto find_or_construct(const std::string& name, Args&&... args) const {
|
||||||
|
return segment_->find_or_construct<T>(name.c_str(), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
[[nodiscard]] auto find(const std::string& name) const { return segment_->find<T>(name.c_str()); }
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_handle_from_address(const void* ptr) const { return segment_->get_handle_from_address(ptr); }
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_address_from_handle(const int64_t handle) const {
|
||||||
|
return segment_->get_address_from_handle(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
[[nodiscard]] auto get_address_from_handle(const int64_t handle) const {
|
||||||
|
return static_cast<T*>(get_address_from_handle(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto destroy(const std::string& name) const { return segment_->destroy<T>(name.c_str()); }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void destroy(T* ptr) const {
|
||||||
|
if (ptr) {
|
||||||
|
ptr->~T();
|
||||||
|
dealloc(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy(const int64_t handle) const {
|
||||||
|
auto ptr = get_address_from_handle(handle);
|
||||||
|
if (ptr)
|
||||||
|
destroy(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_segment_manager() const { return segment_->get_segment_manager(); }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto make_allocator() const { return T{ get_segment_manager() }; }
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_type() const { return type; }
|
||||||
|
private:
|
||||||
|
shm_block_type type;
|
||||||
|
std::shared_ptr<expandable_shm_segment> segment_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* shm_manager 类定义 */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
class shm_mgr : public lazy_singleton<shm_mgr> {
|
||||||
|
public:
|
||||||
|
/* ------------------- 常量 (SHM 名称 & 大小) ------------------- */
|
||||||
|
const char* message_shm_name = "alicho_message_ipc_memory";
|
||||||
|
const char* audio_buffer_shm_name = "alicho_audio_buffer_ipc_memory";
|
||||||
|
const char* string_shm_name = "alicho_string_memory";
|
||||||
|
|
||||||
|
const std::size_t message_shm_size = 1024 * 1024 * 256; // 256 MB
|
||||||
|
const std::size_t audio_buffer_shm_size = 1024 * 1024 * 512; // 512 MB
|
||||||
|
const std::size_t str_shm_size = 1024 * 1024 * 256; // 256 MB
|
||||||
|
|
||||||
|
/* ------------------------- 生命周期 ------------------------- */
|
||||||
|
/**
|
||||||
|
* @brief 初始化共享内存环境
|
||||||
|
* @param in_is_engine true = 引擎进程 (负责创建);false = 插件进程 (只 attach)
|
||||||
|
*
|
||||||
|
* 典型用法:
|
||||||
|
* shm_manager::get_instance().init(is_engine=true);
|
||||||
|
*/
|
||||||
|
void init(bool in_is_engine = false) {
|
||||||
|
is_engine = in_is_engine;
|
||||||
|
allocate_shm(); // 若失败会记录日志并返回 false
|
||||||
|
}
|
||||||
|
|
||||||
|
~shm_mgr() = default; // 析构由 expandable_shm_segment 自动处理
|
||||||
|
|
||||||
|
/* ------------------------ Getter ------------------------ */
|
||||||
|
[[nodiscard]] static auto msg_shm() { return shm_block{ shm_block_type::message, get_instance().message_segment }; }
|
||||||
|
|
||||||
|
[[nodiscard]] static auto ab_shm() { return shm_block{ shm_block_type::audio_buffer, get_instance().audio_buffer_segment }; }
|
||||||
|
|
||||||
|
[[nodiscard]] static auto str_shm() { return shm_block{ shm_block_type::string, get_instance().str_segment }; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
/* --------- 内部实现 --------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 分配 / 连接三块共享内存
|
||||||
|
* @returns true 成功, false 失败 (已记录日志)
|
||||||
|
*/
|
||||||
|
bool allocate_shm();
|
||||||
|
|
||||||
|
/* --------- 成员变量 --------- */
|
||||||
|
bool is_engine = false; // 角色标记
|
||||||
|
std::shared_ptr<expandable_shm_segment> message_segment; // 存放 rpc_message / 队列 等
|
||||||
|
std::shared_ptr<expandable_shm_segment> audio_buffer_segment; // 存放 AudioBuffer
|
||||||
|
std::shared_ptr<expandable_shm_segment> str_segment; // 存放 shm_string
|
||||||
|
};
|
||||||
114
src/backend/src/misc/src/ipc/shm_string.cpp
Normal file
114
src/backend/src/misc/src/ipc/shm_string.cpp
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#include "shm_string.h"
|
||||||
|
|
||||||
|
#include <boost/uuid/random_generator.hpp>
|
||||||
|
#include <boost/uuid/uuid_io.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "shm_mgr.h"
|
||||||
|
|
||||||
|
// 构造函数实现
|
||||||
|
shm_string::shm_string(const std::string& initial_str) { set_string(initial_str); }
|
||||||
|
|
||||||
|
shm_string::shm_string(std::string&& initial_str) { set_string(std::move(initial_str)); }
|
||||||
|
|
||||||
|
shm_string::~shm_string() {
|
||||||
|
// 注意:由于RPC浅拷贝的存在,不能在析构函数中自动销毁
|
||||||
|
// 共享内存对象需要显式调用destroy()方法
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字符串操作实现
|
||||||
|
auto shm_string::get_string() const -> std::string {
|
||||||
|
if (handle == 0) { return {}; }
|
||||||
|
|
||||||
|
return with_shm_string([](const shm_string_row* shm_str) -> std::string {
|
||||||
|
if (shm_str) {
|
||||||
|
// 直接使用数据指针和长度构造,避免c_str()调用
|
||||||
|
return { shm_str->data(), shm_str->size() };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto shm_string::get_string_view() const -> std::string_view {
|
||||||
|
if (handle == 0) { return {}; }
|
||||||
|
|
||||||
|
return with_shm_string([](const shm_string_row* shm_str) -> std::string_view {
|
||||||
|
if (shm_str) { return { shm_str->data(), shm_str->size() }; }
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void shm_string::set_string(const std::string& in_str) {
|
||||||
|
if (handle > 0) { destroy(); }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auto& block = shm_mgr::str_shm();
|
||||||
|
|
||||||
|
// 直接在共享内存中构造字符串
|
||||||
|
const auto shm_str = block.construct<shm_string_row>(in_str.c_str(), block.make_allocator<char_allocator>());
|
||||||
|
if (!shm_str) {
|
||||||
|
handle = 0;
|
||||||
|
throw std::runtime_error("共享内存字符串构造失败");
|
||||||
|
}
|
||||||
|
handle = block.get_handle_from_address(shm_str.to_local());
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
handle = 0;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void shm_string::set_string(std::string&& in_str) {
|
||||||
|
// 对于移动版本,仍需要拷贝到共享内存,但避免额外的拷贝操作
|
||||||
|
set_string(in_str); // 编译器会优化移动操作
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态查询实现
|
||||||
|
auto shm_string::empty() const noexcept -> bool {
|
||||||
|
if (handle == 0) { return true; }
|
||||||
|
|
||||||
|
try { return with_shm_string([](const shm_string_row* shm_str) -> bool { return !shm_str || shm_str->empty(); }); }
|
||||||
|
catch (...) {
|
||||||
|
return true; // 异常情况视为空
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto shm_string::valid() const noexcept -> bool { return handle > 0; }
|
||||||
|
|
||||||
|
auto shm_string::size() const -> size_t {
|
||||||
|
if (handle == 0) { return 0; }
|
||||||
|
|
||||||
|
return with_shm_string([](const shm_string_row* shm_str) -> size_t { return shm_str ? shm_str->size() : 0; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void shm_string::destroy() {
|
||||||
|
if (handle == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auto& block = shm_mgr::str_shm();
|
||||||
|
block.destroy(handle);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
// 析构过程中的异常不应该传播
|
||||||
|
// 可以记录日志但不抛出
|
||||||
|
}
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Func>
|
||||||
|
auto shm_string::with_shm_string(Func&& func) const -> decltype(func(nullptr)) {
|
||||||
|
if (handle == 0) {
|
||||||
|
return func(nullptr);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const auto& block = shm_mgr::str_shm();
|
||||||
|
const auto found = block.get_address_from_handle<shm_string_row>(handle);
|
||||||
|
return func(found);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
return func(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/backend/src/misc/src/ipc/shm_string.h
Normal file
60
src/backend/src/misc/src/ipc/shm_string.h
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <boost/uuid/uuid.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "shm_mgr.h"
|
||||||
|
#include "rpc/common.h"
|
||||||
|
|
||||||
|
using char_allocator = allocator_t<char>;
|
||||||
|
using shm_string_row = bc::basic_string<char, std::char_traits<char>, char_allocator>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 共享内存字符串类 - 支持RPC浅拷贝的跨进程字符串存储
|
||||||
|
*
|
||||||
|
* 重要特性:
|
||||||
|
* - 支持浅拷贝用于RPC传输(只拷贝UUID,不拷贝实际字符串数据)
|
||||||
|
* - 自动资源管理,但需要注意多个实例可能指向同一共享内存对象
|
||||||
|
* - 线程安全的共享内存访问
|
||||||
|
*
|
||||||
|
* RPC使用注意事项:
|
||||||
|
* - 多个shm_string实例可能共享同一个UUID,销毁时需小心
|
||||||
|
* - 建议在RPC调用后立即使用,避免长期持有
|
||||||
|
*/
|
||||||
|
class shm_string {
|
||||||
|
public:
|
||||||
|
// 默认构造和析构
|
||||||
|
shm_string() = default;
|
||||||
|
|
||||||
|
explicit shm_string(const std::string& initial_str);
|
||||||
|
|
||||||
|
explicit shm_string(std::string&& initial_str);
|
||||||
|
|
||||||
|
~shm_string();
|
||||||
|
|
||||||
|
// 字符串操作接口
|
||||||
|
[[nodiscard]] auto get_string() const -> std::string;
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_string_view() const -> std::string_view;
|
||||||
|
|
||||||
|
void set_string(const std::string& in_str);
|
||||||
|
|
||||||
|
void set_string(std::string&& in_str);
|
||||||
|
|
||||||
|
// 状态查询
|
||||||
|
[[nodiscard]] auto empty() const noexcept -> bool;
|
||||||
|
|
||||||
|
[[nodiscard]] auto valid() const noexcept -> bool;
|
||||||
|
|
||||||
|
[[nodiscard]] auto size() const -> size_t;
|
||||||
|
|
||||||
|
// 资源管理
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
void clear() { destroy(); } // 语义化别名
|
||||||
|
private:
|
||||||
|
template<typename Func>
|
||||||
|
auto with_shm_string(Func&& func) const -> decltype(func(nullptr));
|
||||||
|
|
||||||
|
int64_t handle;
|
||||||
|
};
|
||||||
12
src/backend/src/misc/src/lazy_singleton.h
Normal file
12
src/backend/src/misc/src/lazy_singleton.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class lazy_singleton {
|
||||||
|
public:
|
||||||
|
static auto get_instance() -> T& {
|
||||||
|
static T instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
lazy_singleton() = default;
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "library_handle.h"
|
#include "library_handle/library_handle.h"
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
library_handle* library_handle::create(const std::filesystem::path& in_path) {
|
library_handle* library_handle::create(const std::filesystem::path& in_path) {
|
||||||
287
src/backend/src/misc/src/midi_type.h
Normal file
287
src/backend/src/misc/src/midi_type.h
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
#include <expected>
|
||||||
|
#include <format>
|
||||||
|
#include <concepts>
|
||||||
|
#include <array>
|
||||||
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
|
|
||||||
|
namespace midi_type {
|
||||||
|
enum class event_type : std::uint8_t {
|
||||||
|
note_off = 0x80,
|
||||||
|
note_on = 0x90,
|
||||||
|
polyphonic_key_pressure = 0xA0,
|
||||||
|
control_change = 0xB0,
|
||||||
|
program_change = 0xC0,
|
||||||
|
channel_pressure = 0xD0,
|
||||||
|
pitch_bend = 0xE0,
|
||||||
|
system_exclusive = 0xF0,
|
||||||
|
time_code = 0xF1,
|
||||||
|
song_position = 0xF2,
|
||||||
|
song_select = 0xF3,
|
||||||
|
tune_request = 0xF6,
|
||||||
|
timing_clock = 0xF8,
|
||||||
|
start = 0xFA,
|
||||||
|
continue_ = 0xFB,
|
||||||
|
stop = 0xFC,
|
||||||
|
active_sensing = 0xFE,
|
||||||
|
system_reset = 0xFF
|
||||||
|
};
|
||||||
|
|
||||||
|
struct midi_error {
|
||||||
|
enum class code : std::uint8_t {
|
||||||
|
invalid_channel,
|
||||||
|
invalid_velocity,
|
||||||
|
invalid_note,
|
||||||
|
invalid_control_number,
|
||||||
|
invalid_data_byte,
|
||||||
|
invalid_pitch_bend
|
||||||
|
};
|
||||||
|
|
||||||
|
code code;
|
||||||
|
std::string_view message;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>concept midi_data_byte = requires(T value) {
|
||||||
|
requires std::integral<T>; requires (value >= 0 && value <= 127);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>concept midi_channel = requires(T channel) {
|
||||||
|
requires std::integral<T>; requires (channel >= 0 && channel <= 15);
|
||||||
|
};
|
||||||
|
|
||||||
|
class midi_event {
|
||||||
|
public:
|
||||||
|
using time_stamp = std::uint64_t;
|
||||||
|
using data_bytes = std::array<std::uint8_t, 2>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
event_type event_type_;
|
||||||
|
std::uint8_t channel_;
|
||||||
|
data_bytes data_;
|
||||||
|
time_stamp timestamp_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// C++23 designated initializers support
|
||||||
|
struct note_on_data {
|
||||||
|
std::uint8_t note;
|
||||||
|
std::uint8_t velocity;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct note_off_data {
|
||||||
|
std::uint8_t note;
|
||||||
|
std::uint8_t velocity = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct control_change_data {
|
||||||
|
std::uint8_t controller;
|
||||||
|
std::uint8_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct pitch_bend_data {
|
||||||
|
std::uint16_t value; // 14-bit value
|
||||||
|
};
|
||||||
|
|
||||||
|
// Modern constructor with validation
|
||||||
|
constexpr midi_event(event_type type,
|
||||||
|
std::uint8_t channel,
|
||||||
|
data_bytes data = {},
|
||||||
|
time_stamp timestamp = 0) noexcept : event_type_(type),
|
||||||
|
channel_(channel & 0x0F),
|
||||||
|
data_(data),
|
||||||
|
timestamp_(timestamp) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factory methods with compile-time validation
|
||||||
|
[[nodiscard]] static constexpr auto create_note_on(std::uint8_t channel,
|
||||||
|
std::uint8_t note,
|
||||||
|
std::uint8_t velocity,
|
||||||
|
time_stamp timestamp = 0) noexcept -> std::expected<
|
||||||
|
midi_event, midi_error> {
|
||||||
|
if (channel > 15) [[unlikely]] {
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{ .code = midi_error::code::invalid_channel, .message = "Channel must be 0-15" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (note > 127) [[unlikely]] {
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{ .code = midi_error::code::invalid_note, .message = "Note must be 0-127" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (velocity > 127) [[unlikely]] {
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{ .code = midi_error::code::invalid_velocity, .message = "Velocity must be 0-127" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return midi_event{ event_type::note_on, channel, { note, velocity }, timestamp };
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr auto create_note_off(std::uint8_t channel,
|
||||||
|
std::uint8_t note,
|
||||||
|
std::uint8_t velocity = 0,
|
||||||
|
time_stamp timestamp = 0) noexcept -> std::expected<
|
||||||
|
midi_event, midi_error> {
|
||||||
|
if (channel > 15) [[unlikely]] {
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{ .code = midi_error::code::invalid_channel, .message = "Channel must be 0-15" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (note > 127) [[unlikely]] {
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{ .code = midi_error::code::invalid_note, .message = "Note must be 0-127" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (velocity > 127) [[unlikely]] {
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{ .code = midi_error::code::invalid_velocity, .message = "Velocity must be 0-127" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return midi_event{ event_type::note_off, channel, { note, velocity }, timestamp };
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr auto create_control_change(std::uint8_t channel,
|
||||||
|
std::uint8_t controller,
|
||||||
|
std::uint8_t value,
|
||||||
|
time_stamp timestamp = 0) noexcept -> std::expected<
|
||||||
|
midi_event, midi_error> {
|
||||||
|
if (channel > 15) [[unlikely]] {
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{ .code = midi_error::code::invalid_channel, .message = "Channel must be 0-15" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (controller > 127) [[unlikely]] {
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{
|
||||||
|
.code = midi_error::code::invalid_control_number,
|
||||||
|
.message = "Controller must be 0-127"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (value > 127) [[unlikely]] {
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{ .code = midi_error::code::invalid_data_byte, .message = "Value must be 0-127" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return midi_event{ event_type::control_change, channel, { controller, value }, timestamp };
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr auto create_pitch_bend(std::uint8_t channel,
|
||||||
|
std::uint16_t value,
|
||||||
|
time_stamp timestamp = 0) noexcept -> std::expected<
|
||||||
|
midi_event, midi_error> {
|
||||||
|
if (channel > 15) [[unlikely]] {
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{ .code = midi_error::code::invalid_channel, .message = "Channel must be 0-15" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (value > 16383) [[unlikely]] { // 14-bit maximum
|
||||||
|
return std::unexpected{
|
||||||
|
midi_error{ .code = midi_error::code::invalid_pitch_bend, .message = "Pitch bend must be 0-16383" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint8_t lsb = value & 0x7F;
|
||||||
|
std::uint8_t msb = (value >> 7) & 0x7F;
|
||||||
|
|
||||||
|
return midi_event{ event_type::pitch_bend, channel, { lsb, msb }, timestamp };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modern accessors
|
||||||
|
[[nodiscard]] constexpr auto type() const noexcept { return event_type_; }
|
||||||
|
[[nodiscard]] constexpr auto channel() const noexcept { return channel_; }
|
||||||
|
[[nodiscard]] constexpr auto timestamp() const noexcept { return timestamp_; }
|
||||||
|
[[nodiscard]] constexpr auto data() const noexcept { return std::span{ data_ }; }
|
||||||
|
|
||||||
|
// Specific data accessors
|
||||||
|
[[nodiscard]] constexpr std::uint8_t note() const noexcept requires(true) { return data_[0]; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr std::uint8_t velocity() const noexcept { return data_[1]; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr std::uint8_t controller() const noexcept { return data_[0]; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr std::uint8_t control_value() const noexcept { return data_[1]; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr std::uint16_t pitch_bend_value() const noexcept { return data_[0] | (data_[1] << 7); }
|
||||||
|
|
||||||
|
// Serialize to raw MIDI bytes
|
||||||
|
[[nodiscard]] constexpr auto to_bytes() const noexcept -> std::array<std::uint8_t, 3> {
|
||||||
|
std::uint8_t status = static_cast<std::uint8_t>(event_type_) | channel_;
|
||||||
|
return { status, data_[0], data_[1] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modern comparison operators (C++20 spaceship)
|
||||||
|
[[nodiscard]] constexpr auto operator<=>(const midi_event& other) const noexcept = default;
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr bool operator==(const midi_event& other) const noexcept = default;
|
||||||
|
|
||||||
|
// Format support
|
||||||
|
[[nodiscard]] std::string to_string() const {
|
||||||
|
return std::format("MidiEvent{{type: 0x{:02X}, channel: {}, data: [{}, {}], timestamp: {}}}",
|
||||||
|
static_cast<std::uint8_t>(event_type_),
|
||||||
|
channel_,
|
||||||
|
data_[0],
|
||||||
|
data_[1],
|
||||||
|
timestamp_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Modern RAII MIDI Event Buffer
|
||||||
|
class midi_event_buffer {
|
||||||
|
private:
|
||||||
|
std::vector<midi_event> events_;
|
||||||
|
mutable std::shared_mutex mutex_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void add_event(const midi_event& event) {
|
||||||
|
std::unique_lock lock{ mutex_ };
|
||||||
|
events_.emplace_back(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_event(midi_event&& event) {
|
||||||
|
std::unique_lock lock{ mutex_ };
|
||||||
|
events_.emplace_back(std::move(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_events() const -> std::vector<midi_event> {
|
||||||
|
std::shared_lock lock{ mutex_ };
|
||||||
|
return events_;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t size() const noexcept {
|
||||||
|
std::shared_lock lock{ mutex_ };
|
||||||
|
return events_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() noexcept {
|
||||||
|
std::unique_lock lock{ mutex_ };
|
||||||
|
events_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range-based iteration support
|
||||||
|
[[nodiscard]] auto begin() const {
|
||||||
|
std::shared_lock lock{ mutex_ };
|
||||||
|
return events_.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto end() const {
|
||||||
|
std::shared_lock lock{ mutex_ };
|
||||||
|
return events_.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace Midi
|
||||||
|
|
||||||
|
// Custom formatter for std::format
|
||||||
|
template<>
|
||||||
|
struct std::formatter<midi_type::midi_event> {
|
||||||
|
static constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
|
||||||
|
|
||||||
|
static auto format(const midi_type::midi_event& event, std::format_context& ctx) {
|
||||||
|
return std::format_to(ctx.out(), "{}", event.to_string());
|
||||||
|
}
|
||||||
|
};
|
||||||
5
src/backend/src/misc/src/misc_type.h
Normal file
5
src/backend/src/misc/src/misc_type.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
using heartbeat_t = std::chrono::time_point<std::chrono::steady_clock>::duration::rep;
|
||||||
|
using heartbeat_duration_t = std::chrono::steady_clock::duration;
|
||||||
45
src/backend/src/misc/src/rpc/common.h
Normal file
45
src/backend/src/misc/src/rpc/common.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <boost/interprocess/managed_shared_memory.hpp>
|
||||||
|
#include <boost/lockfree/spsc_queue.hpp>
|
||||||
|
#include <boost/container/string.hpp>
|
||||||
|
|
||||||
|
namespace bi = boost::interprocess;
|
||||||
|
namespace bc = boost::container;
|
||||||
|
|
||||||
|
constexpr size_t AUDIO_BLOCK_SIZE = 4096; // 最大音频块大小
|
||||||
|
constexpr size_t QUEUE_CAPACITY = 4; // 音频队列容量
|
||||||
|
constexpr size_t MAX_PAYLOAD_SIZE = 1024; // 最大有效载荷大小
|
||||||
|
constexpr size_t SHM_SIZE = 1024 * 1024; // 共享内存段大小
|
||||||
|
|
||||||
|
struct audio_block {
|
||||||
|
float data[AUDIO_BLOCK_SIZE * 2]; // 音频数据, 双声道
|
||||||
|
size_t size; // 有效数据大小
|
||||||
|
};
|
||||||
|
|
||||||
|
// 单生产者单消费者无锁队列
|
||||||
|
using lock_free_queue = boost::lockfree::spsc_queue<audio_block, boost::lockfree::capacity<QUEUE_CAPACITY>>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using allocator_t = bi::allocator<T, bi::managed_shared_memory::segment_manager>;
|
||||||
|
|
||||||
|
template<typename Msg>
|
||||||
|
struct msg_id_t{};
|
||||||
|
|
||||||
|
// 根据 plugin_id 生成 IPC 资源名称
|
||||||
|
inline std::string get_uds_path() {
|
||||||
|
#if ALICHO_PLATFORM_WINDOWS
|
||||||
|
return "alicho_host.sock";
|
||||||
|
#else
|
||||||
|
return "/tmp/alicho_host"; // Linux 使用 Unix 域套接字
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string get_shm_in_name(const uint32_t plugin_id) {
|
||||||
|
return "alicho_host_in_" + std::to_string(plugin_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string get_shm_out_name(const uint32_t plugin_id) {
|
||||||
|
return "alicho_host_out_" + std::to_string(plugin_id);
|
||||||
|
}
|
||||||
59
src/backend/src/misc/src/rpc/rpc_manager.cpp
Normal file
59
src/backend/src/misc/src/rpc/rpc_manager.cpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 文件: rpc_manager.cpp
|
||||||
|
* 说明: rpc_message_handler 成员函数实现。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rpc_manager.h"
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
// --- RPC 回调注册实现 (Registration) -----------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 为指定的 RPC 消息类型注册/覆盖处理器。
|
||||||
|
*
|
||||||
|
* @param type 消息类型
|
||||||
|
* @param handler 对应处理器
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* • 若 type 已存在,则输出 warn 日志并覆盖旧回调(常见于热更新或重复包含)。
|
||||||
|
*/
|
||||||
|
void rpc_message_handler::register_rpc_handler(rpc::message_type type, rpc_callback handler) {
|
||||||
|
if (handlers_.contains(type)) {
|
||||||
|
spdlog::warn("RPC消息类型 {} 已经注册,覆盖旧处理器", static_cast<uint32_t>(type));
|
||||||
|
}
|
||||||
|
handlers_[type] = std::move(handler); // 覆盖 or 新增
|
||||||
|
}
|
||||||
|
|
||||||
|
void rpc_message_handler::register_rpc_destructor(rpc::message_type type, rpc_destructor destructor) {
|
||||||
|
if (destructors_.contains(type)) {
|
||||||
|
spdlog::warn("RPC消息类型 {} 的析构器已注册,覆盖旧析构器", static_cast<uint32_t>(type));
|
||||||
|
}
|
||||||
|
destructors_[type] = std::move(destructor); // 覆盖 or 新增
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RPC 消息分发实现 (Dispatch) ---------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据 message_id 派发消息到对应处理器
|
||||||
|
*
|
||||||
|
* @param in_msg 收到的 RPC 消息
|
||||||
|
*
|
||||||
|
* 流程:
|
||||||
|
* 1. 在内部 handlers_ 查找对应回调
|
||||||
|
* 2. 找到则调用,未找到则输出 warn
|
||||||
|
*/
|
||||||
|
void rpc_message_handler::handle_message(const rpc_message_t& in_msg) {
|
||||||
|
if (const auto it = handlers_.find(in_msg.message_id); it != handlers_.end()) {
|
||||||
|
#if ALICHO_DEBUG
|
||||||
|
// 条件编译: 调试模式下打印更详细的日志
|
||||||
|
spdlog::info("处理RPC消息,ID: {}", static_cast<uint32_t>(in_msg.message_id));
|
||||||
|
#endif
|
||||||
|
// ⚠ 重要: in_msg.data.data() 为 void* 指向 payload,具体类型由注册器保证
|
||||||
|
it->second(in_msg.get_raw_data());
|
||||||
|
}
|
||||||
|
else { spdlog::warn("未处理的RPC消息类型: {}", static_cast<uint32_t>(in_msg.message_id)); }
|
||||||
|
|
||||||
|
if (const auto dit = destructors_.find(in_msg.message_id); dit != destructors_.end()) {
|
||||||
|
dit->second(in_msg.get_raw_data());
|
||||||
|
}
|
||||||
|
}
|
||||||
176
src/backend/src/misc/src/rpc/rpc_manager.h
Normal file
176
src/backend/src/misc/src/rpc/rpc_manager.h
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* 文件: rpc_manager.h
|
||||||
|
* 描述: 为进程间/线程间 RPC 消息提供统一的注册与分发机制。
|
||||||
|
* 通过 lazy_singleton 保证全局唯一实例,利用无锁共享内存
|
||||||
|
* vector(boost::container::vector)保存消息负载,避免不必要的拷贝。
|
||||||
|
*
|
||||||
|
* 依赖:
|
||||||
|
* • spdlog —— 轻量级日志库
|
||||||
|
* • boost::container::vector —— 支持自定义分配器的 STL-like 容器
|
||||||
|
* • lazy_singleton —— 项目内简单线程安全单例封装
|
||||||
|
* • shm_manager —— 管理跨进程共享内存 + 自定义分配器
|
||||||
|
*
|
||||||
|
* 关键概念:
|
||||||
|
* • rpc_message_t —— 所有 RPC 消息统一封装结构
|
||||||
|
* • rpc_message_handler —— RPC 处理器核心,支持注册 / 分发
|
||||||
|
* • rpc_handler_register —— 基于模板的静态注册工具,结合宏 RPC_REG 使用
|
||||||
|
*
|
||||||
|
* ⚠️ 注意:
|
||||||
|
* 1. 本文件使用了 unordered_map 但未显式包含 <unordered_map>。
|
||||||
|
* 为了自给自足,已在下方 includes 中补充。
|
||||||
|
* 2. 由于模板 + 宏的组合特性,静态注册必须在所有其他源文件静态初始化
|
||||||
|
* 之前完成,否则可能出现“未注册”情况。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// --- 头文件导入 (Headers) ----------------------------------------------------
|
||||||
|
#include <unordered_map> // 用于存放 <message_type, callback>
|
||||||
|
#include <functional> // std::function
|
||||||
|
#include <cstddef> // std::byte
|
||||||
|
#include <boost/container/vector.hpp>
|
||||||
|
|
||||||
|
#include "lazy_singleton.h"
|
||||||
|
#include "rpc_type.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "ipc/shm_mgr.h"
|
||||||
|
|
||||||
|
namespace bc = boost::container;
|
||||||
|
|
||||||
|
// --- 类型别名与前向声明 (Type Aliases & FWD Decls) --------------------------
|
||||||
|
using rpc_callback = std::function<void(const void*)>; // 统一 RPC 回调签名
|
||||||
|
using rpc_destructor = std::function<void(const void*)>; // 统一析构回调签名
|
||||||
|
|
||||||
|
// --- 数据结构定义 (Structs) --------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct rpc_message_t
|
||||||
|
* @brief RPC 消息载体, 将消息 ID 与二进制负载包装为一个整体。
|
||||||
|
*
|
||||||
|
* 设计思路:
|
||||||
|
* • 使用 boost::container::vector 搭配 shm_manager 提供的自定义分配器,
|
||||||
|
* 直接把 payload 放到共享内存,以降低跨进程数据拷贝成本。
|
||||||
|
* • 默认构造时 message_id 置为 WTF(一个非法占位值),防止误用。
|
||||||
|
*/
|
||||||
|
struct rpc_message_t {
|
||||||
|
template<typename T>
|
||||||
|
void make_data() {
|
||||||
|
static_assert(std::is_standard_layout_v<T>, "RPC 消息负载必须是标准布局类型 (standard-layout type)");
|
||||||
|
const auto& block = shm_mgr::msg_shm();
|
||||||
|
auto ptr = block.construct<T>();
|
||||||
|
handle = block.get_handle_from_address(ptr);
|
||||||
|
}
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
void make_data(Args&&... args) {
|
||||||
|
static_assert(std::is_standard_layout_v<T>, "RPC 消息负载必须是标准布局类型 (standard-layout type)");
|
||||||
|
const auto& block = shm_mgr::msg_shm();
|
||||||
|
auto ptr = block.construct<T>(std::forward<Args>(args)...);
|
||||||
|
handle = block.get_handle_from_address(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto get_data() const -> T* {
|
||||||
|
static_assert(std::is_standard_layout_v<T>, "RPC 消息负载必须是标准布局类型 (standard-layout type)");
|
||||||
|
const auto& block = shm_mgr::msg_shm();
|
||||||
|
return block.get_address_from_handle<T>(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto get_raw_data() const -> void* {
|
||||||
|
return shm_mgr::get_instance().msg_shm().get_address_from_handle(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc::message_type message_id; // 消息类型 ID
|
||||||
|
int64_t handle; // 共享内存句柄 (跨进程时使用)
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 核心业务类 (Core Business Class) ---------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class rpc_message_handler
|
||||||
|
* @brief 全局唯一的 RPC 分发器:
|
||||||
|
* 1. 提供 register_rpc_handler() 进行回调注册
|
||||||
|
* 2. 提供 handle_message() 统一派发入口
|
||||||
|
*
|
||||||
|
* 继承 lazy_singleton 保证线程安全 + 懒加载单例。
|
||||||
|
*/
|
||||||
|
class rpc_message_handler : public lazy_singleton<rpc_message_handler> {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 注册新的 RPC 处理回调
|
||||||
|
*
|
||||||
|
* @param type 要注册的消息类型
|
||||||
|
* @param handler 对应的处理器函数
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* rpc_message_handler::get_instance().register_rpc_handler(
|
||||||
|
* rpc::message_type::Ping,
|
||||||
|
* [](const void* p){ static_cast<const PingMsg*>(p)->process(); }
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
void register_rpc_handler(rpc::message_type type, rpc_callback handler);
|
||||||
|
|
||||||
|
void register_rpc_destructor(rpc::message_type type, rpc_destructor destructor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据 message_id 分发 RPC 消息
|
||||||
|
*
|
||||||
|
* @param in_msg 收到的完整消息结构体
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* rpc_message_t msg = receive_from_queue(...);
|
||||||
|
* rpc_message_handler::get_instance().handle_message(msg);
|
||||||
|
*/
|
||||||
|
void handle_message(const rpc_message_t& in_msg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 储存 <message_type, 处理回调>
|
||||||
|
std::unordered_map<rpc::message_type, rpc_callback> handlers_;
|
||||||
|
// 存储 <message_type, 析构回调>
|
||||||
|
std::unordered_map<rpc::message_type, rpc_destructor> destructors_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 模板工具 & 宏 (Template Helpers & Macros) -------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template Msg
|
||||||
|
* @struct rpc_handler_register
|
||||||
|
* @brief 静态注册器。构造时即完成回调注册,用于零侵入式绑定。
|
||||||
|
*
|
||||||
|
* 用法:
|
||||||
|
* struct Pong { void process() {} };
|
||||||
|
* inline static auto reg = rpc_handler_register<Pong>(rpc::message_type::Pong);
|
||||||
|
*/
|
||||||
|
template<typename Msg>
|
||||||
|
struct rpc_handler_register {
|
||||||
|
explicit rpc_handler_register(rpc::message_type in_type) {
|
||||||
|
static_assert(requires { Msg::rpc_id; }, "Msg 必须具有静态成员 'rpc_id', 是否定义了DEFINE_ID宏?");
|
||||||
|
static_assert(requires { std::declval<Msg>().process(); }, "Msg 必须具有成员函数 'process()'。");
|
||||||
|
|
||||||
|
// 封装真正的回调,实现与 Msg::process() 解耦
|
||||||
|
auto real_func = [](const void* payload) {
|
||||||
|
((Msg*)payload)->process();
|
||||||
|
};
|
||||||
|
rpc_message_handler::get_instance().register_rpc_handler(in_type, real_func);
|
||||||
|
|
||||||
|
// 如果Msg有destroy成员函数, 则注册析构器
|
||||||
|
if constexpr (requires { std::declval<Msg>().destroy(); }) {
|
||||||
|
auto real_destructor = [](const void* payload) {
|
||||||
|
((Msg*)payload)->destroy();
|
||||||
|
};
|
||||||
|
rpc_message_handler::get_instance().register_rpc_destructor(in_type, real_destructor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 语法糖宏,配合静态变量实现“一行注册”。
|
||||||
|
* @code
|
||||||
|
* struct Foo { void process(); };
|
||||||
|
* RPC_REG(FooType, Foo)
|
||||||
|
*
|
||||||
|
* 会展开为:
|
||||||
|
* inline static auto Foo_register = rpc_handler_register<Foo>(rpc::message_type::FooType);
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
#define RPC_REG(type, t) \
|
||||||
|
inline static auto t##_register = rpc_handler_register<t>(rpc::message_type::type);
|
||||||
71
src/backend/src/misc/src/rpc/rpc_type.h
Normal file
71
src/backend/src/misc/src/rpc/rpc_type.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// RPC调用消息体结构, 由AudioEngine和PluginHost之间传输
|
||||||
|
#pragma once
|
||||||
|
#include <boost/container/vector.hpp>
|
||||||
|
#include <boost/uuid.hpp>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include "ipc/shm_mgr.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "ipc/shm_string.h"
|
||||||
|
|
||||||
|
namespace bc = boost::container;
|
||||||
|
|
||||||
|
#define DEFINE_ID(type) static constexpr auto rpc_id = rpc::message_type::##type;
|
||||||
|
|
||||||
|
namespace rpc {
|
||||||
|
enum class message_type : uint32_t {
|
||||||
|
WTF = 0, // 未知消息类型, 用于调试
|
||||||
|
|
||||||
|
// Host -> AudioEngine
|
||||||
|
LOG = 1, // 日志消息
|
||||||
|
REGISTER_HOST, // 注册插件
|
||||||
|
PROCESS_DONE, // 处理完成
|
||||||
|
PARAMETER_VALUE_CHANGED, // 参数值改变
|
||||||
|
|
||||||
|
// AudioEngine -> Host
|
||||||
|
PROCESS_AUDIO_BLOCK = 10001, // 处理音频块
|
||||||
|
SET_PARAMETER, // 设置参数
|
||||||
|
SHUTDOWN // 关闭插件
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host -> AudioEngine
|
||||||
|
namespace engine_rpc {
|
||||||
|
struct log {
|
||||||
|
DEFINE_ID(LOG)
|
||||||
|
|
||||||
|
void set_str(const std::string& in_str) {
|
||||||
|
str_.set_string(in_str);
|
||||||
|
}
|
||||||
|
void set_level(spdlog::level::level_enum in_level) {
|
||||||
|
level_ = in_level;
|
||||||
|
}
|
||||||
|
|
||||||
|
// destroy函数由rpc_manager自动调用
|
||||||
|
void destroy() {
|
||||||
|
str_.destroy();
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
shm_string str_;
|
||||||
|
spdlog::level::level_enum level_ = spdlog::level::info;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// AudioEngine -> Host
|
||||||
|
namespace host_rpc {
|
||||||
|
struct load_plugin {
|
||||||
|
// DEFINE_ID(REGISTER_HOST)
|
||||||
|
|
||||||
|
// void set_plugin_path(const std::string& in_path) {
|
||||||
|
// plugin_path_.set_string(in_path);
|
||||||
|
// }
|
||||||
|
// void set_plugin_id(const boost::uuids::uuid& in_id) {
|
||||||
|
// plugin_id_ = in_id;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // destroy函数由rpc_manager自动调用
|
||||||
|
// void destroy() {
|
||||||
|
// plugin_path_.destroy();
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -15,10 +15,6 @@ retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
|
|||||||
add_executable(${PROJECT_NAME} ${SRC_FILES})
|
add_executable(${PROJECT_NAME} ${SRC_FILES})
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
config_target
|
config_target
|
||||||
alicho_proto
|
|
||||||
gRPC::grpc++
|
|
||||||
protobuf::libprotobuf
|
|
||||||
libzmq
|
|
||||||
glfw
|
glfw
|
||||||
AlichoMisc
|
AlichoMisc
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,32 +1,98 @@
|
|||||||
#include "GLFW/glfw3.h"
|
#include "GLFW/glfw3.h"
|
||||||
#include <iostream>
|
#include <thread>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
|
#include "rpc/common.h"
|
||||||
#include "vst2host.h"
|
#include "vst2host.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
#include <boost/interprocess/managed_shared_memory.hpp>
|
||||||
|
|
||||||
|
#include "ipc/ipc_node.h"
|
||||||
|
|
||||||
|
namespace bi = boost::interprocess;
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
if (argc != 2)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
shm_mgr::get_instance().init();
|
||||||
|
|
||||||
|
plugin_ipc_node node(argv[1]);
|
||||||
|
|
||||||
|
float data_counter = 0;
|
||||||
|
while (true) {
|
||||||
|
if (!node.is_engine_running())
|
||||||
|
break;
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
node.process_rpc();
|
||||||
|
// 获取一个音频块
|
||||||
|
auto pending_block = node.get_audio_rb().get_pending_block();
|
||||||
|
if (pending_block.data) {
|
||||||
|
// 将待处理的数据写入插件输出
|
||||||
|
for (size_t i = 0; i < pending_block.size; ++i) {
|
||||||
|
pending_block.data[i] = data_counter;
|
||||||
|
data_counter += 0.01f;
|
||||||
|
if (data_counter > 1.0f)
|
||||||
|
data_counter = -1.0f;
|
||||||
|
}
|
||||||
|
node.get_audio_rb().complete_pending_block();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
std::string shm_input_path = argv[1];
|
||||||
|
std::string shm_output_path = argv[2];
|
||||||
|
|
||||||
|
boost::system::error_code ec;
|
||||||
|
boost::asio::io_context io_context;
|
||||||
|
boost::asio::local::stream_protocol::socket socket(io_context);
|
||||||
|
socket.connect(get_uds_path(), ec);
|
||||||
|
if (ec) {
|
||||||
|
spdlog::error("无法连接到插件主机: {}", ec.message());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// 连接共享内存
|
||||||
|
bi::managed_shared_memory input_segment(bi::open_only, shm_input_path.c_str());
|
||||||
|
bi::managed_shared_memory output_segment(bi::open_only, shm_output_path.c_str());
|
||||||
|
// 获取锁无阻塞队列
|
||||||
|
auto input_queue = input_segment.find<lock_free_queue>("input_queue").first;
|
||||||
|
auto output_queue = output_segment.find<lock_free_queue>("output_queue").first;
|
||||||
|
if (!input_queue || !output_queue) {
|
||||||
|
spdlog::error("无法找到共享内存队列");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
glfwSetErrorCallback([](int error, const char* description) {
|
glfwSetErrorCallback([](int error, const char* description) {
|
||||||
std::println(std::cerr, "GLFW Error {}: {}", error, description);
|
spdlog::error("GLFW 错误 {}: {}", error, description);
|
||||||
});
|
});
|
||||||
glfwInit(); // 初始化 GLFW
|
glfwInit(); // 初始化 GLFW
|
||||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // 仅使用 GLFW 的窗口功能,不使用 OpenGL
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // 仅使用 GLFW 的窗口功能,不使用 OpenGL
|
||||||
|
|
||||||
auto plugin_path = argv[1];
|
|
||||||
|
|
||||||
load_plugin(plugin_path, 44100, 512);
|
|
||||||
open_editor();
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (is_editor_open()) {
|
// if (is_editor_open()) {
|
||||||
glfwPollEvents();
|
// glfwPollEvents();
|
||||||
idle_editor();
|
// idle_editor();
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
break;
|
// break;
|
||||||
|
// }
|
||||||
|
// 处理音频块
|
||||||
|
audio_block block;
|
||||||
|
if (input_queue->pop(block)) {
|
||||||
|
break; // 这里临时测试, 直接退出
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr std::chrono::nanoseconds delta_time(std::nano::den / 320); // 240fps刷新率,但是留有额外的抖动余量(防止CPU时间片分配不均)
|
||||||
|
std::this_thread::sleep_for(delta_time);
|
||||||
}
|
}
|
||||||
unload_plugin();
|
unload_plugin();
|
||||||
|
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
|
|
||||||
|
bi::shared_memory_object::remove(shm_input_path.c_str());
|
||||||
|
bi::shared_memory_object::remove(shm_output_path.c_str());
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/backend/src/vst2_host/src/rpc.cpp
Normal file
1
src/backend/src/vst2_host/src/rpc.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "rpc.h"
|
||||||
5
src/backend/src/vst2_host/src/rpc.h
Normal file
5
src/backend/src/vst2_host/src/rpc.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace vst2_rpc {
|
||||||
|
|
||||||
|
};
|
||||||
@@ -61,7 +61,7 @@ inline VstIntPtr VSTCALLBACK host_callback(AEffect* effect,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_plugin(const std::filesystem::path& in_lib_path, float in_sample_rate, int in_block_size) {
|
void load_plugin(const std::filesystem::path& in_lib_path, float in_sample_rate, int32_t in_block_size) {
|
||||||
lib_ = library_handle::create(in_lib_path);
|
lib_ = library_handle::create(in_lib_path);
|
||||||
auto main_entry = lib_->get_func<vst_plugin_entry>("VSTPluginMain");
|
auto main_entry = lib_->get_func<vst_plugin_entry>("VSTPluginMain");
|
||||||
if (!main_entry) {
|
if (!main_entry) {
|
||||||
|
|||||||
@@ -2,21 +2,23 @@
|
|||||||
#include "pluginterfaces/vst2.x/aeffectx.h" // Steinberg 官方头
|
#include "pluginterfaces/vst2.x/aeffectx.h" // Steinberg 官方头
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
#include "library_handle.h"
|
#include "library_handle/library_handle.h"
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
#include "size_type.h"
|
#include "audio_ring_buffer.h"
|
||||||
|
#include "vec.h"
|
||||||
|
|
||||||
|
|
||||||
inline library_handle* lib_ = nullptr;
|
inline library_handle* lib_ = nullptr;
|
||||||
inline AEffect* effect_ = nullptr;
|
inline AEffect* effect_ = nullptr;
|
||||||
inline float current_sample_rate;
|
inline float current_sample_rate;
|
||||||
inline int32_t current_block_size;
|
inline int32_t current_block_size;
|
||||||
|
inline uint32_t plugin_id = 0; // 插件 ID
|
||||||
inline double current_bpm = 120.0; // 默认 120 BPM
|
inline double current_bpm = 120.0; // 默认 120 BPM
|
||||||
inline GLFWwindow* window_handle = nullptr;
|
inline GLFWwindow* window_handle = nullptr;
|
||||||
inline std::optional<i_vec2> editor_pos{}; // 编辑器最后位置
|
inline std::optional<i_vec2> editor_pos{}; // 编辑器最后位置
|
||||||
|
|
||||||
void load_plugin(const std::filesystem::path& in_lib_path, float in_sample_rate, int in_block_size);
|
void load_plugin(const std::filesystem::path& in_lib_path, float in_sample_rate, int32_t in_block_size);
|
||||||
void unload_plugin();
|
void unload_plugin();
|
||||||
|
|
||||||
/* -------- 调度分发 -------- */
|
/* -------- 调度分发 -------- */
|
||||||
@@ -45,7 +47,7 @@ inline void set_sample_rate(float in_sample_rate) {
|
|||||||
current_sample_rate = in_sample_rate;
|
current_sample_rate = in_sample_rate;
|
||||||
dispatch(effSetSampleRate, 0, 0, nullptr, in_sample_rate);
|
dispatch(effSetSampleRate, 0, 0, nullptr, in_sample_rate);
|
||||||
}
|
}
|
||||||
inline void set_block_size(int in_block_size) {
|
inline void set_block_size(int32_t in_block_size) {
|
||||||
current_block_size = in_block_size;
|
current_block_size = in_block_size;
|
||||||
dispatch(effSetBlockSize, 0, in_block_size);
|
dispatch(effSetBlockSize, 0, in_block_size);
|
||||||
}
|
}
|
||||||
@@ -119,6 +121,11 @@ inline void idle_editor() {
|
|||||||
dispatch(effEditIdle);
|
dispatch(effEditIdle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief 获取插件的 chunk 数据
|
||||||
|
/// @param index 0 → Bank(fxb):包含插件的全部 program/parameter 状态
|
||||||
|
/// 1 → Program(fxp):仅包含当前 program 的状态
|
||||||
|
/// @return chunk数据
|
||||||
inline std::vector<uint8_t> get_chunk(int index) {
|
inline std::vector<uint8_t> get_chunk(int index) {
|
||||||
std::vector<uint8_t> chunk;
|
std::vector<uint8_t> chunk;
|
||||||
const auto size = dispatch(effGetChunk, index, 0, nullptr);
|
const auto size = dispatch(effGetChunk, index, 0, nullptr);
|
||||||
@@ -128,6 +135,10 @@ inline std::vector<uint8_t> get_chunk(int index) {
|
|||||||
}
|
}
|
||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief 设置插件的 chunk 数据
|
||||||
|
/// @param chunk 要设置的 chunk 数据
|
||||||
|
/// @param index 0为bank, 1为program
|
||||||
inline void set_chunk(const std::span<uint8_t>& chunk, int index) {
|
inline void set_chunk(const std::span<uint8_t>& chunk, int index) {
|
||||||
if (chunk.empty())
|
if (chunk.empty())
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
|
|||||||
add_executable(${PROJECT_NAME} ${SRC_FILES})
|
add_executable(${PROJECT_NAME} ${SRC_FILES})
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
config_target
|
config_target
|
||||||
alicho_proto
|
AlichoProto
|
||||||
gRPC::grpc++
|
gRPC::grpc++
|
||||||
protobuf::libprotobuf
|
protobuf::libprotobuf
|
||||||
libzmq
|
libzmq
|
||||||
|
|||||||
@@ -1,11 +1,30 @@
|
|||||||
{
|
{
|
||||||
"dependencies": [
|
"name" : "alicho",
|
||||||
"grpc",
|
"version" : "0.0.1",
|
||||||
"protobuf",
|
"builtin-baseline" : "f33cc491c85a7d643c5ab6da1667c1458e6d7abf",
|
||||||
"cppzmq",
|
"dependencies" : [ "grpc", "protobuf", "cppzmq", "glfw3", "spdlog", "gtest", {
|
||||||
"glfw3",
|
"name" : "boost-asio",
|
||||||
"gtest"
|
"version>=" : "1.88.0"
|
||||||
],
|
}, {
|
||||||
"version": "0.0.1",
|
"name" : "boost-process",
|
||||||
"name": "ninaengine"
|
"version>=" : "1.88.0"
|
||||||
|
}, {
|
||||||
|
"name" : "boost-interprocess",
|
||||||
|
"version>=" : "1.88.0"
|
||||||
|
}, {
|
||||||
|
"name" : "boost-lockfree",
|
||||||
|
"version>=" : "1.88.0"
|
||||||
|
}, {
|
||||||
|
"name" : "tbb",
|
||||||
|
"version>=" : "2022.1.0"
|
||||||
|
}, {
|
||||||
|
"name" : "boost-circular-buffer",
|
||||||
|
"version>=" : "1.88.0"
|
||||||
|
}, {
|
||||||
|
"name" : "boost-uuid",
|
||||||
|
"version>=" : "1.88.0"
|
||||||
|
}, {
|
||||||
|
"name" : "boost-thread",
|
||||||
|
"version>=" : "1.88.0"
|
||||||
|
} ]
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,20 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Daw.Project;
|
||||||
|
using Grpc.Core;
|
||||||
|
|
||||||
namespace frontend.Views;
|
namespace frontend.Views;
|
||||||
|
|
||||||
|
class DawProjectSync : Sync.SyncBase
|
||||||
|
{
|
||||||
|
public override Task Stream(IAsyncStreamReader<UpdateWrapper> requestStream, IServerStreamWriter<UpdateWrapper> responseStream, ServerCallContext context)
|
||||||
|
{
|
||||||
|
// responseStream.WriteAsync();
|
||||||
|
// requestStream.Current.YUpdate;
|
||||||
|
return base.Stream(requestStream, responseStream, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
|
|||||||
@@ -36,9 +36,14 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="NetMQ" Version="4.0.2.1" />
|
<PackageReference Include="NetMQ" Version="4.0.2.1" />
|
||||||
|
<PackageReference Include="YDotNet" Version="0.4.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Protobuf Include="..\proto\*.proto" GrpcServices="Client" />
|
<Protobuf Include="..\proto\**\*.proto"
|
||||||
|
GrpcServices="Both"
|
||||||
|
Link="Protos\%(RecursiveDir)%(Filename)%(Extension)"
|
||||||
|
ProtoRoot="..\proto"
|
||||||
|
/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
29
src/proto/common/domain.proto
Normal file
29
src/proto/common/domain.proto
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package daw.common;
|
||||||
|
|
||||||
|
enum SampleFormat { F32 = 0; F64 = 1; I16 = 2; }
|
||||||
|
|
||||||
|
message PluginId { uint32 value = 1; }
|
||||||
|
message ParameterId { uint32 value = 1; }
|
||||||
|
|
||||||
|
message ParameterChange {
|
||||||
|
PluginId plugin_id = 1;
|
||||||
|
ParameterId parameter_id = 2;
|
||||||
|
float value = 3;
|
||||||
|
uint64 sample_offset = 4; // 相对当前 block
|
||||||
|
}
|
||||||
|
|
||||||
|
message MidiEvent {
|
||||||
|
PluginId plugin_id = 1;
|
||||||
|
bytes raw_midi = 2; // 1~3 字节
|
||||||
|
uint64 sample_offset = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AudioBufferRef {
|
||||||
|
PluginId plugin_id = 1;
|
||||||
|
uint64 shm_id = 2;
|
||||||
|
uint32 offset = 3;
|
||||||
|
uint32 frames = 4;
|
||||||
|
SampleFormat format = 5;
|
||||||
|
uint32 channels = 6;
|
||||||
|
}
|
||||||
@@ -2,19 +2,21 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package daw.api;
|
package daw.api;
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Service Definitions (gRPC Control Plane)
|
// Service Definitions (gRPC Control Plane)
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
service TransportService {
|
service TransportService {
|
||||||
rpc Play(Empty) returns (StatusResponse);
|
rpc Play(google.protobuf.Empty) returns (StatusResponse);
|
||||||
rpc Pause(Empty) returns (StatusResponse);
|
rpc Pause(google.protobuf.Empty) returns (StatusResponse);
|
||||||
rpc Stop(Empty) returns (StatusResponse);
|
rpc Stop(google.protobuf.Empty) returns (StatusResponse);
|
||||||
rpc SetTempo(SetTempoRequest) returns (StatusResponse);
|
rpc SetTempo(SetTempoRequest) returns (StatusResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
service ProjectService {
|
service ProjectService {
|
||||||
rpc NewProject(Empty) returns (ProjectState);
|
rpc NewProject(google.protobuf.Empty) returns (ProjectState);
|
||||||
rpc LoadProject(LoadProjectRequest) returns (ProjectState);
|
rpc LoadProject(LoadProjectRequest) returns (ProjectState);
|
||||||
rpc SaveProject(SaveProjectRequest) returns (StatusResponse);
|
rpc SaveProject(SaveProjectRequest) returns (StatusResponse);
|
||||||
}
|
}
|
||||||
@@ -35,8 +37,6 @@ service PluginService {
|
|||||||
// Message Definitions
|
// Message Definitions
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
message Empty {}
|
|
||||||
|
|
||||||
message StatusResponse {
|
message StatusResponse {
|
||||||
bool success = 1;
|
bool success = 1;
|
||||||
string error_message = 2;
|
string error_message = 2;
|
||||||
84
src/proto/ctrl/project_sync.proto
Normal file
84
src/proto/ctrl/project_sync.proto
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package daw.project;
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
// DAW工程数据同步服务
|
||||||
|
|
||||||
|
/* ---- 业务层 ---- */
|
||||||
|
message MidiNote {
|
||||||
|
uint32 id = 1;
|
||||||
|
uint32 pitch = 2; // MIDI note number, e.g., 60 for Middle C
|
||||||
|
uint32 start = 3; // midi tick
|
||||||
|
uint32 length = 4; // midi tick
|
||||||
|
uint32 velocity = 5; // 0-127
|
||||||
|
}
|
||||||
|
|
||||||
|
message MidiClip {
|
||||||
|
bytes plugin_uuid = 1; // 所属插件的uuid
|
||||||
|
string name = 2;
|
||||||
|
repeated MidiNote notes = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AutomationClipPoint {
|
||||||
|
uint32 id = 1; // 自动化控制点的唯一ID
|
||||||
|
uint32 tick = 2; // 相对于Clip中的位置 midi tick
|
||||||
|
float value = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AutomationClip {
|
||||||
|
bytes plugin_instance_id = 1; // 所属插件实例ID
|
||||||
|
string name = 2;
|
||||||
|
uint32 param_id = 3;
|
||||||
|
repeated AutomationClipPoint points = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Clip {
|
||||||
|
bytes uuid = 1; // 128bit UUID
|
||||||
|
string name = 2;
|
||||||
|
repeated MidiClip midi_clip = 3;
|
||||||
|
repeated AutomationClip automation_clips = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClipInstance {
|
||||||
|
bytes uuid = 1;
|
||||||
|
uint64 clip_id = 2; // 引用Clip的ID
|
||||||
|
double clip_start = 3; // 相对于clip的起点 midi tick
|
||||||
|
double clip_end = 4; // 相对于clip的终点 midi tick
|
||||||
|
double instance_start = 5; // 全局位置 midi tick
|
||||||
|
double instance_end = 6; // 全局位置 midi tick
|
||||||
|
}
|
||||||
|
|
||||||
|
message Track {
|
||||||
|
bytes uuid = 1;
|
||||||
|
string name = 2;
|
||||||
|
bool is_muted = 3;
|
||||||
|
repeated ClipInstance clips = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Plugin {
|
||||||
|
bytes uuid = 1; // 工程内唯一ID
|
||||||
|
uint64 plugin_id = 2; // 对应音频插件的ID
|
||||||
|
string name = 3;
|
||||||
|
bool is_bypassed = 4;
|
||||||
|
bytes preset = 5; // 二进制预设数据
|
||||||
|
}
|
||||||
|
|
||||||
|
message Project {
|
||||||
|
bytes id = 1;
|
||||||
|
string name = 2;
|
||||||
|
double bpm = 3;
|
||||||
|
repeated Clip clips = 4;
|
||||||
|
repeated Track tracks = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- CRDT 增量封装 ---- */
|
||||||
|
message UpdateWrapper {
|
||||||
|
bytes y_update = 1; // yrs/yjs 二进制 diff
|
||||||
|
uint64 schema_hash = 2; // 快速校验业务 Schema
|
||||||
|
google.protobuf.Timestamp ts = 3; // 服务端时间戳
|
||||||
|
}
|
||||||
|
|
||||||
|
service Sync {
|
||||||
|
rpc Stream (stream UpdateWrapper) returns (stream UpdateWrapper);
|
||||||
|
}
|
||||||
15
src/proto/rt/rt_envelope.proto
Normal file
15
src/proto/rt/rt_envelope.proto
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package daw.rt;
|
||||||
|
|
||||||
|
import "common/domain.proto";
|
||||||
|
|
||||||
|
message RTEnvelope {
|
||||||
|
uint64 seq = 1;
|
||||||
|
uint64 timestamp_us = 2;
|
||||||
|
|
||||||
|
oneof payload {
|
||||||
|
daw.common.ParameterChange param_change = 10;
|
||||||
|
daw.common.MidiEvent midi = 11;
|
||||||
|
daw.common.AudioBufferRef buffer_ref = 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user