split the "C++ client bindings" properly
There probably isn't that much utility to doing this, but now the shared/ code can be imported to write a hazelnut client application in C++. Not that that's of any use to me, but seperating it means it should also be easier to work on. No more strange compile errors because clangd is confused and dumb.
This commit is contained in:
parent
0e37c40a5e
commit
e0b12c9729
7 changed files with 149 additions and 84 deletions
|
@ -7,12 +7,17 @@ fn main() {
|
|||
println!("cargo:rerun-if-changed=src/rust_wrapper.cpp");
|
||||
println!("cargo:rerun-if-changed=../shared/src");
|
||||
|
||||
// Build the required shared C++ bits, and the glue C++ code
|
||||
// that we can import to Rust. (Since the API surface is so small,
|
||||
// and not in danger of changing in a huge way, I am opting for manual
|
||||
// bindings instead of using a crate like the `cxx` crate. Sue me.)
|
||||
build
|
||||
.emit_rerun_if_env_changed(true)
|
||||
.cpp(true)
|
||||
.std("c++20")
|
||||
.include("../shared/src")
|
||||
.file("../shared/src/ivshmem.cpp")
|
||||
.file("../shared/src/client.cpp")
|
||||
.file("src/rust_wrapper.cpp")
|
||||
.compile("hazelnut_client_cpp_native");
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ pub(crate) mod sys {
|
|||
|
||||
#[repr(u32)]
|
||||
#[allow(unused)] // it IS used, just not by Rust
|
||||
pub enum ResultCode {
|
||||
pub enum ClientResultCode {
|
||||
/// The frame has not been changed. (This is implemented internally by comparing the frame serial; if it has changed then the frame has too)
|
||||
Unchanged,
|
||||
|
||||
|
@ -26,7 +26,7 @@ pub(crate) mod sys {
|
|||
|
||||
pub(crate) fn rust_hazelnut_client_open(client: *mut ffi::c_void, fd: ffi::c_int) -> bool;
|
||||
|
||||
pub(crate) fn rust_hazelnut_client_tick(client: *mut ffi::c_void) -> ResultCode;
|
||||
pub(crate) fn rust_hazelnut_client_tick(client: *mut ffi::c_void) -> ClientResultCode;
|
||||
|
||||
// Explicitly lock/unlock.
|
||||
pub(crate) fn rust_hazelnut_client_lock(client: *mut ffi::c_void);
|
||||
|
@ -39,7 +39,7 @@ pub(crate) mod sys {
|
|||
}
|
||||
}
|
||||
|
||||
pub type ResultCode = sys::ResultCode;
|
||||
pub type ResultCode = sys::ClientResultCode;
|
||||
|
||||
/// A Hazelnut IVSHMEM client. Wraps the C++ code (see ./rust_wrapper.cpp and the shared sources)
|
||||
/// so that we can use it in Rust, semi-safely. (Note that I really do not like the current
|
||||
|
|
|
@ -1,84 +1,20 @@
|
|||
#include "ivshmem.hpp"
|
||||
#include "ivshmem_protocol.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "client.hpp"
|
||||
|
||||
enum class ResultCode : u32 { Unchanged, Changed, LockContended, Fail };
|
||||
|
||||
struct HazelnutIvshmemClient {
|
||||
bool Open(int fd) {
|
||||
if(!ivshmemDevice.Open(fd))
|
||||
return false;
|
||||
|
||||
auto* ptr = (u8*)ivshmemDevice.GetPointer();
|
||||
|
||||
pHeader = (hazelnut::IvshHeader*)&ptr[0];
|
||||
pFrameHeader = (hazelnut::FrameHeader*)&ptr[0x1000];
|
||||
|
||||
sessionId = pHeader->serverSessionId.load();
|
||||
lastFrameSerial = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE: This function leaves the lock held on successful returns.
|
||||
ResultCode TickOne() {
|
||||
if(!pHeader)
|
||||
return ResultCode::Fail;
|
||||
|
||||
if(sessionId != pHeader->serverSessionId.load())
|
||||
return ResultCode::Fail;
|
||||
|
||||
if(pHeader->lock.try_lock_manually()) {
|
||||
// failed to lock
|
||||
return ResultCode::LockContended;
|
||||
}
|
||||
|
||||
auto current = pFrameHeader->serial.load();
|
||||
if(current == lastFrameSerial) {
|
||||
return ResultCode::Unchanged;
|
||||
}
|
||||
|
||||
lastFrameSerial = current;
|
||||
return ResultCode::Changed;
|
||||
}
|
||||
|
||||
void Lock() { pHeader->lock.lock_manually(); }
|
||||
|
||||
void Unlock() { pHeader->lock.unlock(); }
|
||||
|
||||
u32* Framebuffer() {
|
||||
if(pFrameHeader) {
|
||||
return pFrameHeader->bits();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
hazelnut::IvshmemDevice ivshmemDevice;
|
||||
|
||||
// Protocol/memory stuff
|
||||
hazelnut::IvshHeader* pHeader;
|
||||
hazelnut::FrameHeader* pFrameHeader;
|
||||
|
||||
u32 sessionId {};
|
||||
|
||||
u32 lastFrameSerial {};
|
||||
};
|
||||
|
||||
// Rust bindings
|
||||
// Rust bindings to the C++ Hazelnut client library
|
||||
|
||||
extern "C" {
|
||||
void* rust_new_hazelnut_client() {
|
||||
return (void*)new HazelnutIvshmemClient;
|
||||
return (void*)new hazelnut::Client;
|
||||
}
|
||||
|
||||
void rust_destroy_hazelnut_client(void* pClient) {
|
||||
if(pClient)
|
||||
delete(HazelnutIvshmemClient*)pClient;
|
||||
delete(hazelnut::Client*)pClient;
|
||||
}
|
||||
|
||||
bool rust_hazelnut_client_open(void* pClient, int fd) {
|
||||
if(pClient) {
|
||||
return ((HazelnutIvshmemClient*)pClient)->Open(fd);
|
||||
return ((hazelnut::Client*)pClient)->Open(fd);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -86,41 +22,41 @@ bool rust_hazelnut_client_open(void* pClient, int fd) {
|
|||
|
||||
int rust_hazelnut_client_tick(void* pClient) {
|
||||
if(pClient) {
|
||||
return (int)((HazelnutIvshmemClient*)pClient)->TickOne();
|
||||
return (int)((hazelnut::Client*)pClient)->TickOne();
|
||||
}
|
||||
return (int)ResultCode::Fail;
|
||||
return (int)hazelnut::Client::ResultCode::Fail;
|
||||
}
|
||||
|
||||
void rust_hazelnut_client_lock(void* pClient) {
|
||||
if(pClient) {
|
||||
return ((HazelnutIvshmemClient*)pClient)->Lock();
|
||||
return ((hazelnut::Client*)pClient)->Lock();
|
||||
}
|
||||
}
|
||||
|
||||
void rust_hazelnut_client_unlock(void* pClient) {
|
||||
if(pClient) {
|
||||
return ((HazelnutIvshmemClient*)pClient)->Unlock();
|
||||
return ((hazelnut::Client*)pClient)->Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
u32* rust_hazelnut_client_get_framebuffer(void* pClient) {
|
||||
if(pClient) {
|
||||
return ((HazelnutIvshmemClient*)pClient)->Framebuffer();
|
||||
return ((hazelnut::Client*)pClient)->Framebuffer();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 rust_hazelnut_client_get_width(void* pClient) {
|
||||
if(pClient) {
|
||||
return ((HazelnutIvshmemClient*)pClient)->pFrameHeader->width.load();
|
||||
if(pClient) {
|
||||
return ((hazelnut::Client*)pClient)->GetWidth();
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 rust_hazelnut_client_get_height(void* pClient) {
|
||||
if(pClient) {
|
||||
return ((HazelnutIvshmemClient*)pClient)->pFrameHeader->height.load();
|
||||
if(pClient) {
|
||||
return ((hazelnut::Client*)pClient)->GetHeight();
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -2,4 +2,6 @@
|
|||
|
||||
This is all of the things shared between the agent and client ends.
|
||||
|
||||
It is cross platform C++20.
|
||||
It is cross platform C++20.
|
||||
|
||||
(Some of this should be moved to a client-cpp directory, probably.)
|
78
shared/src/client.cpp
Normal file
78
shared/src/client.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#include "client.hpp"
|
||||
|
||||
#include "atomic_spinlock.hpp"
|
||||
#include "ivshmem_protocol.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace hazelnut {
|
||||
|
||||
bool Client::Open(int fd) {
|
||||
// We probably should have this return a tuple of <success, errno>
|
||||
// so that we can pass the errno to Rust in a semi-sane manner,
|
||||
// and actually communicate that. However, Rust/the code opening the file
|
||||
// will handle file open errors which is probably the brink of most of the errors possible.
|
||||
if(!ivshmemDevice.Open(fd))
|
||||
return false;
|
||||
|
||||
auto* ptr = (u8*)ivshmemDevice.GetPointer();
|
||||
|
||||
pHeader = (hazelnut::IvshHeader*)&ptr[0];
|
||||
pFrameHeader = (hazelnut::FrameHeader*)&ptr[0x1000];
|
||||
|
||||
sessionId = pHeader->serverSessionId.load();
|
||||
lastFrameSerial = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
Client::ResultCode Client::TickOne() {
|
||||
if(!pHeader)
|
||||
return ResultCode::Fail;
|
||||
|
||||
if(sessionId != pHeader->serverSessionId.load())
|
||||
return ResultCode::Fail;
|
||||
|
||||
if(pHeader->lock.try_lock_manually()) {
|
||||
// failed to lock
|
||||
return ResultCode::LockContended;
|
||||
}
|
||||
|
||||
auto current = pFrameHeader->serial.load();
|
||||
if(current == lastFrameSerial) {
|
||||
return ResultCode::Unchanged;
|
||||
}
|
||||
|
||||
lastFrameSerial = current;
|
||||
return ResultCode::Changed;
|
||||
}
|
||||
|
||||
void Client::Lock() {
|
||||
pHeader->lock.lock_manually();
|
||||
}
|
||||
|
||||
void Client::Unlock() {
|
||||
pHeader->lock.unlock();
|
||||
}
|
||||
|
||||
u32* Client::Framebuffer() {
|
||||
if(pFrameHeader) {
|
||||
return pFrameHeader->bits();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 Client::GetWidth() {
|
||||
if(pFrameHeader)
|
||||
return pFrameHeader->width.load();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 Client::GetHeight() {
|
||||
if(pFrameHeader)
|
||||
return pFrameHeader->width.load();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace hazelnut
|
40
shared/src/client.hpp
Normal file
40
shared/src/client.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
#include "ivshmem.hpp"
|
||||
|
||||
namespace hazelnut {
|
||||
struct IvshHeader;
|
||||
struct FrameHeader;
|
||||
|
||||
struct Client {
|
||||
enum class ResultCode : u32 { Unchanged, Changed, LockContended, Fail };
|
||||
|
||||
bool Open(int fd);
|
||||
|
||||
// NOTE: This function leaves the lock held on successful returns.
|
||||
ResultCode TickOne();
|
||||
|
||||
void Lock();
|
||||
|
||||
void Unlock();
|
||||
|
||||
// Only call these methods when the lock is held.
|
||||
|
||||
u32* Framebuffer();
|
||||
|
||||
u32 GetWidth();
|
||||
|
||||
u32 GetHeight();
|
||||
|
||||
private:
|
||||
hazelnut::IvshmemDevice ivshmemDevice;
|
||||
|
||||
// Protocol/memory stuff
|
||||
hazelnut::IvshHeader* pHeader;
|
||||
hazelnut::FrameHeader* pFrameHeader;
|
||||
|
||||
u32 sessionId {};
|
||||
|
||||
u32 lastFrameSerial {};
|
||||
};
|
||||
|
||||
} // namespace hazelnut
|
|
@ -30,6 +30,10 @@ namespace hazelnut {
|
|||
std::atomic<u32> width{};
|
||||
std::atomic<u32> height{};
|
||||
|
||||
// FIXME: We should have a frame format, with the following possible types
|
||||
// RGBA - The current RGBA backed framebuffefr
|
||||
// H264 - NVENC H.264 (we should probably also have a flag for frame type as well)
|
||||
|
||||
// obtain bits at the next page boundary
|
||||
u32* bits() {
|
||||
return reinterpret_cast<u32*>(reinterpret_cast<u8*>(this) + 0x1000);
|
||||
|
|
Loading…
Reference in a new issue