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:
Lily Tsuru 2024-12-21 21:40:20 -05:00
parent 0e37c40a5e
commit e0b12c9729
7 changed files with 149 additions and 84 deletions

View file

@ -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");
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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
View 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
View 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

View file

@ -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);