[libcamera-devel,v2,11/14] py: New event handling
diff mbox series

Message ID 20220629070416.17550-12-tomi.valkeinen@ideasonboard.com
State Superseded
Headers show
Series
  • Python bindings event handling
Related show

Commit Message

Tomi Valkeinen June 29, 2022, 7:04 a.m. UTC
At the moment the Python bindings only handle the requestCompleted
events. But we have others, bufferCompleted and disconnect from the
Camera, and cameraAdded and cameraRemoved from the CameraManager.

This makes all those events available to the users.

The get_ready_requests() method is now deprecated, but available to keep
backward compatibility.

The new event handling works as follows:

The user sets callbacks to the CameraManager or Cameras (e.g.
Camera.buffer_completed). When the event_fd informs of an event, the
user must call CameraManager.dispatch_events() which gets the queued
events and calls the relevant callbacks for each queued event.

Additionally there is CameraManager.discard_events() if the user does
not want to process the events but wants to clear the event queue (e.g.
after stopping the cameras or when exiting the app).

I'm not very happy with this patch. It works fine, but there's a lot of
repetition of almost-the-same code. Perhaps some template magics might
reduce the repetition, but I also fear that it can easily make the code
more difficult to read.

TODO: Documentation.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 src/py/libcamera/py_camera_manager.cpp | 250 +++++++++++++++++++++++--
 src/py/libcamera/py_camera_manager.h   |  64 ++++++-
 src/py/libcamera/py_main.cpp           |  89 ++++++++-
 3 files changed, 381 insertions(+), 22 deletions(-)

Patch
diff mbox series

diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp
index 3dd8668e..599a9f7e 100644
--- a/src/py/libcamera/py_camera_manager.cpp
+++ b/src/py/libcamera/py_camera_manager.cpp
@@ -58,6 +58,7 @@  py::list PyCameraManager::cameras()
 	return l;
 }
 
+/* DEPRECATED */
 std::vector<py::object> PyCameraManager::getReadyRequests()
 {
 	int ret = readFd();
@@ -70,21 +71,234 @@  std::vector<py::object> PyCameraManager::getReadyRequests()
 
 	std::vector<py::object> py_reqs;
 
-	for (Request *request : getCompletedRequests()) {
-		py::object o = py::cast(request);
-		/* Decrease the ref increased in Camera.queue_request() */
-		o.dec_ref();
-		py_reqs.push_back(o);
+	for (const auto &ev : getEvents()) {
+		switch (ev.type_) {
+		case CameraEvent::Type::RequestCompleted: {
+			py::object o = py::cast(ev.request_);
+			/* Decrease the ref increased in Camera.queue_request() */
+			o.dec_ref();
+			py_reqs.push_back(o);
+		}
+		default:
+			/* ignore */
+			break;
+		}
 	}
 
 	return py_reqs;
 }
 
 /* Note: Called from another thread */
-void PyCameraManager::handleRequestCompleted(Request *req)
+void PyCameraManager::handleBufferCompleted(std::shared_ptr<Camera> cam, Request *req, FrameBuffer *fb)
 {
-	pushRequest(req);
-	writeFd();
+	CameraEvent ev(CameraEvent::Type::BufferCompleted, cam);
+	ev.request_ = req;
+	ev.fb_ = fb;
+
+	pushEvent(ev);
+}
+
+/* Note: Called from another thread */
+void PyCameraManager::handleRequestCompleted(std::shared_ptr<Camera> cam, Request *req)
+{
+	CameraEvent ev(CameraEvent::Type::RequestCompleted, cam);
+	ev.request_ = req;
+
+	pushEvent(ev);
+}
+
+/* Note: Called from another thread */
+void PyCameraManager::handleDisconnected(std::shared_ptr<Camera> cam)
+{
+	CameraEvent ev(CameraEvent::Type::Disconnect, cam);
+
+	pushEvent(ev);
+}
+
+/* Note: Called from another thread */
+void PyCameraManager::handleCameraAdded(std::shared_ptr<Camera> cam)
+{
+	CameraEvent ev(CameraEvent::Type::CameraAdded, cam);
+
+	pushEvent(ev);
+}
+
+/* Note: Called from another thread */
+void PyCameraManager::handleCameraRemoved(std::shared_ptr<Camera> cam)
+{
+	CameraEvent ev(CameraEvent::Type::CameraRemoved, cam);
+
+	pushEvent(ev);
+}
+
+void PyCameraManager::dispatchEvents()
+{
+	int ret = readFd();
+
+	if (ret == EAGAIN) {
+		LOG(Python, Debug) << "No events to dispatch";
+		return;
+	}
+
+	if (ret != 0)
+		throw std::system_error(ret, std::generic_category());
+
+	std::vector<CameraEvent> events = getEvents();
+
+	LOG(Python, Debug) << "Dispatch " << events.size() << " events";
+
+	for (const auto &event : events) {
+		std::shared_ptr<Camera> camera = event.camera_;
+
+		switch (event.type_) {
+		case CameraEvent::Type::CameraAdded: {
+			if (cameraAddedHandler_)
+				cameraAddedHandler_(camera);
+
+			break;
+		}
+		case CameraEvent::Type::CameraRemoved: {
+			if (cameraRemovedHandler_)
+				cameraRemovedHandler_(camera);
+
+			break;
+		}
+		case CameraEvent::Type::BufferCompleted: {
+			auto cb = getBufferCompleted(camera.get());
+			if (cb)
+				cb(camera, event.request_, event.fb_);
+
+			break;
+		}
+		case CameraEvent::Type::RequestCompleted: {
+			auto cb = getRequestCompleted(camera.get());
+			if (cb)
+				cb(camera, event.request_);
+
+			/* Decrease the ref increased in Camera.queue_request() */
+			py::object o = py::cast(event.request_);
+			o.dec_ref();
+
+			break;
+		}
+		case CameraEvent::Type::Disconnect: {
+			auto cb = getDisconnected(camera.get());
+			if (cb)
+				cb(camera);
+
+			break;
+		}
+		default:
+			ASSERT(false);
+		}
+	}
+}
+
+void PyCameraManager::discardEvents()
+{
+	int ret = readFd();
+
+	if (ret == EAGAIN)
+		return;
+
+	if (ret != 0)
+		throw std::system_error(ret, std::generic_category());
+
+	std::vector<CameraEvent> v = getEvents();
+
+	LOG(Python, Debug) << "Discard " << v.size() << " events";
+
+	for (const auto &ev : v) {
+		if (ev.type_ != CameraEvent::Type::RequestCompleted)
+			continue;
+
+		/* Decrease the ref increased in Camera.queue_request() */
+		py::object o = py::cast(ev.request_);
+		o.dec_ref();
+	}
+}
+
+std::function<void(std::shared_ptr<Camera>)> PyCameraManager::getCameraAdded() const
+{
+	return cameraAddedHandler_;
+}
+
+void PyCameraManager::setCameraAdded(std::function<void(std::shared_ptr<Camera>)> func)
+{
+	if (cameraAddedHandler_)
+		cameraManager_->cameraAdded.disconnect();
+
+	cameraAddedHandler_ = func;
+
+	if (func)
+		cameraManager_->cameraAdded.connect(this, &PyCameraManager::handleCameraAdded);
+}
+
+std::function<void(std::shared_ptr<Camera>)> PyCameraManager::getCameraRemoved() const
+{
+	return cameraRemovedHandler_;
+}
+
+void PyCameraManager::setCameraRemoved(std::function<void(std::shared_ptr<Camera>)> func)
+{
+	if (cameraRemovedHandler_)
+		cameraManager_->cameraRemoved.disconnect();
+
+	cameraRemovedHandler_ = func;
+
+	if (func)
+		cameraManager_->cameraRemoved.connect(this, &PyCameraManager::handleCameraRemoved);
+}
+
+std::function<void(std::shared_ptr<Camera>, Request *)> PyCameraManager::getRequestCompleted(Camera *cam)
+{
+	if (auto it = cameraRequestCompletedHandlers_.find(cam);
+	    it != cameraRequestCompletedHandlers_.end())
+		return it->second;
+
+	return nullptr;
+}
+
+void PyCameraManager::setRequestCompleted(Camera *cam, std::function<void(std::shared_ptr<Camera>, Request *)> func)
+{
+	if (func)
+		cameraRequestCompletedHandlers_[cam] = func;
+	else
+		cameraRequestCompletedHandlers_.erase(cam);
+}
+
+std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> PyCameraManager::getBufferCompleted(Camera *cam)
+{
+	if (auto it = cameraBufferCompletedHandlers_.find(cam);
+	    it != cameraBufferCompletedHandlers_.end())
+		return it->second;
+
+	return nullptr;
+}
+
+void PyCameraManager::setBufferCompleted(Camera *cam, std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> func)
+{
+	if (func)
+		cameraBufferCompletedHandlers_[cam] = func;
+	else
+		cameraBufferCompletedHandlers_.erase(cam);
+}
+
+std::function<void(std::shared_ptr<Camera>)> PyCameraManager::getDisconnected(Camera *cam)
+{
+	if (auto it = cameraDisconnectHandlers_.find(cam);
+	    it != cameraDisconnectHandlers_.end())
+		return it->second;
+
+	return nullptr;
+}
+
+void PyCameraManager::setDisconnected(Camera *cam, std::function<void(std::shared_ptr<Camera>)> func)
+{
+	if (func)
+		cameraDisconnectHandlers_[cam] = func;
+	else
+		cameraDisconnectHandlers_.erase(cam);
 }
 
 void PyCameraManager::writeFd()
@@ -110,16 +324,22 @@  int PyCameraManager::readFd()
 	return 0;
 }
 
-void PyCameraManager::pushRequest(Request *req)
+void PyCameraManager::pushEvent(const CameraEvent &ev)
 {
-	MutexLocker guard(completedRequestsMutex_);
-	completedRequests_.push_back(req);
+	MutexLocker guard(cameraEventsMutex_);
+	cameraEvents_.push_back(ev);
+
+	writeFd();
+
+	LOG(Python, Debug) << "Queued events: " << cameraEvents_.size();
 }
 
-std::vector<Request *> PyCameraManager::getCompletedRequests()
+std::vector<PyCameraManager::CameraEvent> PyCameraManager::getEvents()
 {
-	std::vector<Request *> v;
-	MutexLocker guard(completedRequestsMutex_);
-	swap(v, completedRequests_);
+	std::vector<CameraEvent> v;
+
+	MutexLocker guard(cameraEventsMutex_);
+	swap(v, cameraEvents_);
+
 	return v;
 }
diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h
index 3525057d..aa51a6bc 100644
--- a/src/py/libcamera/py_camera_manager.h
+++ b/src/py/libcamera/py_camera_manager.h
@@ -30,16 +30,70 @@  public:
 
 	void handleRequestCompleted(Request *req);
 
+	void handleBufferCompleted(std::shared_ptr<Camera> cam, Request *req, FrameBuffer *fb);
+	void handleRequestCompleted(std::shared_ptr<Camera> cam, Request *req);
+	void handleDisconnected(std::shared_ptr<Camera> cam);
+	void handleCameraAdded(std::shared_ptr<Camera> cam);
+	void handleCameraRemoved(std::shared_ptr<Camera> cam);
+
+	void dispatchEvents();
+	void discardEvents();
+
+	std::function<void(std::shared_ptr<Camera>)> getCameraAdded() const;
+	void setCameraAdded(std::function<void(std::shared_ptr<Camera>)> func);
+
+	std::function<void(std::shared_ptr<Camera>)> getCameraRemoved() const;
+	void setCameraRemoved(std::function<void(std::shared_ptr<Camera>)> func);
+
+	std::function<void(std::shared_ptr<Camera>, Request *)> getRequestCompleted(Camera *cam);
+	void setRequestCompleted(Camera *cam, std::function<void(std::shared_ptr<Camera>, Request *)> func);
+
+	std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> getBufferCompleted(Camera *cam);
+	void setBufferCompleted(Camera *cam, std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> func);
+
+	std::function<void(std::shared_ptr<Camera>)> getDisconnected(Camera *cam);
+	void setDisconnected(Camera *cam, std::function<void(std::shared_ptr<Camera>)> func);
+
 private:
+	struct CameraEvent {
+		enum class Type {
+			Undefined = 0,
+			CameraAdded,
+			CameraRemoved,
+			Disconnect,
+			RequestCompleted,
+			BufferCompleted,
+		};
+
+		CameraEvent(Type type, std::shared_ptr<Camera> camera)
+			: type_(type), camera_(camera)
+		{
+		}
+
+		Type type_;
+
+		std::shared_ptr<Camera> camera_;
+
+		Request *request_ = nullptr;
+		FrameBuffer *fb_ = nullptr;
+	};
+
 	std::unique_ptr<CameraManager> cameraManager_;
 
 	UniqueFD eventFd_;
-	libcamera::Mutex completedRequestsMutex_;
-	std::vector<Request *> completedRequests_
-		LIBCAMERA_TSA_GUARDED_BY(completedRequestsMutex_);
+	libcamera::Mutex cameraEventsMutex_;
+	std::vector<CameraEvent> cameraEvents_
+		LIBCAMERA_TSA_GUARDED_BY(cameraEvents_);
+
+	std::function<void(std::shared_ptr<Camera>)> cameraAddedHandler_;
+	std::function<void(std::shared_ptr<Camera>)> cameraRemovedHandler_;
+
+	std::map<Camera *, std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)>> cameraBufferCompletedHandlers_;
+	std::map<Camera *, std::function<void(std::shared_ptr<Camera>, Request *)>> cameraRequestCompletedHandlers_;
+	std::map<Camera *, std::function<void(std::shared_ptr<Camera>)>> cameraDisconnectHandlers_;
 
 	void writeFd();
 	int readFd();
-	void pushRequest(Request *req);
-	std::vector<Request *> getCompletedRequests();
+	void pushEvent(const CameraEvent &ev);
+	std::vector<CameraEvent> getEvents();
 };
diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp
index 1ef1384e..a07f06c4 100644
--- a/src/py/libcamera/py_main.cpp
+++ b/src/py/libcamera/py_main.cpp
@@ -107,7 +107,20 @@  PYBIND11_MODULE(_libcamera, m)
 		.def_property_readonly("cameras", &PyCameraManager::cameras)
 
 		.def_property_readonly("event_fd", &PyCameraManager::eventFd)
-		.def("get_ready_requests", &PyCameraManager::getReadyRequests);
+
+		/* DEPRECATED */
+		.def("get_ready_requests", &PyCameraManager::getReadyRequests)
+
+		.def("dispatch_events", &PyCameraManager::dispatchEvents)
+		.def("discard_events", &PyCameraManager::discardEvents)
+
+		.def_property("camera_added",
+		              &PyCameraManager::getCameraAdded,
+		              &PyCameraManager::setCameraAdded)
+
+		.def_property("camera_removed",
+		              &PyCameraManager::getCameraRemoved,
+		              &PyCameraManager::setCameraRemoved);
 
 	pyCamera
 		.def_property_readonly("id", &Camera::id)
@@ -131,7 +144,14 @@  PYBIND11_MODULE(_libcamera, m)
 			auto cm = gCameraManager.lock();
 			ASSERT(cm);
 
-			self.requestCompleted.connect(cm.get(), &PyCameraManager::handleRequestCompleted);
+			/*
+			 * Note: We always subscribe requestComplete, as the bindings
+			 * use requestComplete event to decrement the Request refcount-
+			 */
+
+			self.requestCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req) {
+				cm->handleRequestCompleted(camera, req);
+			});
 
 			ControlList controlList(self.controls());
 
@@ -159,6 +179,71 @@  PYBIND11_MODULE(_libcamera, m)
 				                        "Failed to start camera");
 		})
 
+		.def_property("request_completed",
+		[](Camera &self) {
+			auto cm = gCameraManager.lock();
+			ASSERT(cm);
+
+			return cm->getRequestCompleted(&self);
+		},
+		[](Camera &self, std::function<void(std::shared_ptr<Camera>, Request *)> f) {
+			auto cm = gCameraManager.lock();
+			ASSERT(cm);
+
+			cm->setRequestCompleted(&self, f);
+
+			/*
+			 * Note: We do not subscribe requestComplete here, as we
+			 * do that in the start() method.
+			 */
+		})
+
+		.def_property("buffer_completed",
+		[](Camera &self) -> std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> {
+			auto cm = gCameraManager.lock();
+			ASSERT(cm);
+
+			return cm->getBufferCompleted(&self);
+		},
+		[](Camera &self, std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> f) {
+			auto cm = gCameraManager.lock();
+			ASSERT(cm);
+
+			cm->setBufferCompleted(&self, f);
+
+			self.bufferCompleted.disconnect();
+
+			if (!f)
+				return;
+
+			self.bufferCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req, FrameBuffer *fb) {
+				cm->handleBufferCompleted(camera, req, fb);
+			});
+		})
+
+		.def_property("disconnected",
+		[](Camera &self) -> std::function<void(std::shared_ptr<Camera>)> {
+			auto cm = gCameraManager.lock();
+			ASSERT(cm);
+
+			return cm->getDisconnected(&self);
+		},
+		[](Camera &self, std::function<void(std::shared_ptr<Camera>)> f) {
+			auto cm = gCameraManager.lock();
+			ASSERT(cm);
+
+			cm->setDisconnected(&self, f);
+
+			self.disconnected.disconnect();
+
+			if (!f)
+				return;
+
+			self.disconnected.connect(&self, [cm, camera=self.shared_from_this()]() {
+				cm->handleDisconnected(camera);
+			});
+		})
+
 		.def("__str__", [](Camera &self) {
 			return "<libcamera.Camera '" + self.id() + "'>";
 		})