Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async Idea #47

Open
johnwstanford opened this issue Jan 23, 2021 · 3 comments
Open

Async Idea #47

johnwstanford opened this issue Jan 23, 2021 · 3 comments

Comments

@johnwstanford
Copy link

I've got an idea on how to make the async API work. I've got it working on a local branch and it seems to be working pretty well. It basically revolves around a struct that looks like this:

pub struct AsyncTransfer {
	pub ptr: *mut libusb_transfer,
	pub buff: Vec<u8>,
	pub recv: Pin<Box<VecDeque<TransferStatus>>>,
}

The buff vector is the vector where libusb puts the data. ptr is the transfer struct provided by libusb. recv is where we accumulate the results. TransferStatus looks like this:

pub enum TransferStatus {
	Completed(Vec<u8>),
	Error,
	TimedOut,
	Stall,
	NoDevice,
	Overflow,
	Unknown(i32)
}

The callback function is a private function inside impl AsynTransfer and looks like this:

	extern "system" fn callback(xfer_ptr: *mut libusb_transfer) {

		unsafe {
			let xfer = match (*xfer_ptr).status {
				0 => {
					// Clone the memory into a vector
					let slice:&[u8] = std::slice::from_raw_parts((*xfer_ptr).buffer, (*xfer_ptr).actual_length as usize);
					TransferStatus::Completed(slice.to_vec())
				},
				1 => TransferStatus::Error,
				2 => TransferStatus::TimedOut,
				3 => TransferStatus::Stall,
				4 => TransferStatus::NoDevice,
				5 => TransferStatus::Overflow,
				n => TransferStatus::Unknown(n),
			};

			// Update the parser stored in the user data field
			let xfer_deque:*mut VecDeque<TransferStatus> = (*xfer_ptr).user_data as *mut VecDeque<TransferStatus>;
			(*xfer_deque).push_back(xfer);

			// Resubmit the transfer
			assert!(libusb1_sys::libusb_submit_transfer(xfer_ptr) == 0);
		}

	}

Basically, it puts the result into a VecDeque and the scope that owns the struct can process these whenever it wants. This is not threadsafe, but doesn't pretend to be. If you try to make this struct Send or Sync, the compiler gives an error. The callback function runs in the same thread that created the struct when you call libusb1_sys::libusb_handle_events.

Like I said, I've been testing it and it seems to work pretty well. If you're interested, I'll push the branch and create a pull request if you give me the permissions I need.

@johnwstanford
Copy link
Author

A few more comments on the AsyncTransfer struct. For this to work, all three of these need to have the same lifetime and making them all owned by the same struct accomplishes this. We also use Pin for the VecDeque that collects the results because a pointer to this is passed to the libusb transfer struct as the user data, so it needs to maintain the same location in memory for as long as it lives.

@a1ien
Copy link
Owner

a1ien commented Jan 23, 2021

If you're interested, I'll push the branch and create a pull request if you give me the permissions I need.

You don't need any permission for this. You can create fork and push any branch to it.

@johnwstanford
Copy link
Author

Ok. Cool. I didn't know that. Just created one :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants