monitor texture and shader file writes using inotify: print file events v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sat, 02 Dec 2023 15:02:56 +0100
branchv_0
changeset 7 e6065118326f
parent 6 fd93a46db15b
child 8 80ad08521091
monitor texture and shader file writes using inotify: print file events
FileMonitor.cpp
FileMonitor.h
Makefile
Shark.cpp
Shark.h
nbproject/configurations.xml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/FileMonitor.cpp	Sat Dec 02 15:02:56 2023 +0100
@@ -0,0 +1,127 @@
+/**
+ * ShaderShark
+ * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string>
+#include <stdexcept>
+#include <map>
+#include <sys/inotify.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "FileMonitor.h"
+
+void checkError(bool hasError, const std::string& message) {
+	if (hasError) throw std::logic_error(message + strerror(errno));
+}
+
+class WatchedFile::Impl {
+public:
+	int fd;
+	int wd;
+	std::string fileName;
+	uint32_t mask;
+	std::shared_ptr<FileMonitor::Impl> fileMonitorImpl;
+
+	virtual ~Impl();
+};
+
+WatchedFile::WatchedFile() {
+	impl = std::make_shared<Impl>();
+}
+
+WatchedFile::~WatchedFile() {
+}
+
+class FileMonitor::Impl {
+public:
+	int fd;
+	std::map<int, std::string> fileNames;
+
+	void unwatch(int wd) {
+		int result = inotify_rm_watch(fd, wd);
+		checkError(result < 0, "unable to stop monitoring a file: ");
+		fileNames.erase(wd);
+	}
+
+	virtual ~Impl() {
+		close(fd);
+	}
+
+};
+
+FileMonitor::FileMonitor() : FileMonitor(std::make_shared<Impl>()) {
+}
+
+FileMonitor::FileMonitor(std::shared_ptr<Impl> impl) {
+	this->impl = impl;
+	impl->fd = inotify_init1(IN_NONBLOCK);
+	checkError(impl->fd < 0, "unable to initialize FileMonitor: ");
+}
+
+FileMonitor::~FileMonitor() {
+}
+
+int FileMonitor::getFD() const {
+	return impl->fd;
+}
+
+WatchedFile FileMonitor::watch(const std::string& fileName) {
+	return watch(fileName, IN_CLOSE_WRITE);
+}
+
+WatchedFile FileMonitor::watch(const std::string& fileName, uint32_t mask) {
+	int wd = inotify_add_watch(impl->fd, fileName.c_str(), mask);
+	checkError(wd < 0, "unable to monitor a file: ");
+	impl->fileNames[wd] = fileName;
+	WatchedFile wf;
+	wf.impl->fd = impl->fd;
+	wf.impl->wd = wd;
+	wf.impl->fileMonitorImpl = impl;
+	wf.impl->fileName = fileName;
+	wf.impl->mask = mask;
+	return wf;
+}
+
+bool FileMonitor::readEvent(FileEvent& event) {
+	struct inotify_event rawEvent;
+	int result = ::read(impl->fd, &rawEvent, sizeof (rawEvent));
+	if (result == sizeof (rawEvent)) {
+		event.fileName = impl->fileNames[rawEvent.wd];
+		event.mask = rawEvent.mask;
+		return true;
+	} else {
+		return false;
+	}
+}
+
+WatchedFile::Impl::~Impl() {
+	fileMonitorImpl->unwatch(wd);
+}
+
+FileMonitor WatchedFile::getFileMonitor() const {
+	return FileMonitor(impl->fileMonitorImpl);
+}
+
+std::string WatchedFile::getFileName() const {
+	return impl->fileName;
+}
+
+uint32_t WatchedFile::getMask() const {
+	return impl->mask;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/FileMonitor.h	Sat Dec 02 15:02:56 2023 +0100
@@ -0,0 +1,70 @@
+/**
+ * ShaderShark
+ * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <string>
+#include <memory>
+
+class FileMonitor;
+
+class WatchedFile {
+public:
+	class Impl;
+	WatchedFile();
+	virtual ~WatchedFile();
+	std::string getFileName() const;
+	uint32_t getMask() const;
+	FileMonitor getFileMonitor() const;
+private:
+	std::shared_ptr<Impl> impl;
+	friend FileMonitor;
+};
+
+class FileEvent {
+public:
+	std::string fileName;
+	uint32_t mask;
+	// TODO: more information, see: man 7 inotify
+};
+
+class FileMonitor {
+public:
+	class Impl;
+	FileMonitor();
+	virtual ~FileMonitor();
+	/**
+	 * @return file descriptor that can be monitored by e.g. epoll
+	 */
+	int getFD() const;
+	/**
+	 * @param fileName file to be monitored
+	 * @param mask IN_CLOSE_WRITE and other IN_*
+	 * @return monitoring will continue until any copy of this object exists
+	 */
+	WatchedFile watch(const std::string& fileName);
+	WatchedFile watch(const std::string& fileName, uint32_t mask);
+	/**
+	 * @param event object to be filled with information
+	 * @return true if event was filled, call this method until false returned
+	 */
+	bool readEvent(FileEvent& event);
+private:
+	std::shared_ptr<Impl> impl;
+	FileMonitor(std::shared_ptr<Impl> impl);
+	friend WatchedFile;
+};
--- a/Makefile	Fri Dec 01 21:02:02 2023 +0100
+++ b/Makefile	Sat Dec 02 15:02:56 2023 +0100
@@ -26,7 +26,13 @@
 build:
 	mkdir -p $(@)
 	
-SRC=Shark.cpp shader-shark.cpp ImageLoader.cpp Shader.cpp Program.cpp
+SRC= \
+    Shark.cpp \
+    shader-shark.cpp \
+    ImageLoader.cpp \
+    Shader.cpp \
+    Program.cpp \
+    FileMonitor.cpp
 
 build/shader-shark: $(SRC) build *.h
 	$(CXX) -std=c++20 -g -o $(@) $(SRC) $$(pkg-config --cflags --libs \
--- a/Shark.cpp	Fri Dec 01 21:02:02 2023 +0100
+++ b/Shark.cpp	Sat Dec 02 15:02:56 2023 +0100
@@ -102,6 +102,7 @@
 	int x11fd = XConnectionNumber(dpy);
 	EPoll epoll;
 	epoll.add(x11fd);
+	epoll.add(fileMonitor.getFD());
 	try {
 		epoll.add(setNonBlocking(STDIN_FILENO));
 	} catch (const EPoll::Exception& e) {
@@ -211,6 +212,14 @@
 				}
 				logOutput << std::endl;
 
+			} else if (epoll[epollEvent].data.fd == fileMonitor.getFD()) {
+				std::cout << "FileMonitor event:" << std::endl;
+				for (FileEvent fe; fileMonitor.readEvent(fe);) {
+					std::cout << "   "
+							<< " file: " << fe.fileName
+							<< " mask: " << fe.mask
+							<< std::endl;
+				}
 			} else {
 				logOutput
 						<< "error: event on an unexpected FD: "
@@ -353,6 +362,7 @@
 void Shark::loadTextures() {
 	for (const Configuration::Texture& tex : cfg.textures) {
 		textures.push_back(loadTexture(tex.fileName));
+		watchedFiles.push_back(fileMonitor.watch(tex.fileName));
 		// TODO: review texture loading and binding
 		// works even without this - default texture
 		// glUniform1i(ProgAttr.jazz, jazz);
@@ -400,6 +410,7 @@
 
 			program->attachShader(*shader.get());
 			shaders.push_back(shader);
+			watchedFiles.push_back(fileMonitor.watch(fileName));
 			std::cerr << "GLSL loaded: " << fileName.c_str() << std::endl;
 		}
 
--- a/Shark.h	Fri Dec 01 21:02:02 2023 +0100
+++ b/Shark.h	Sat Dec 02 15:02:56 2023 +0100
@@ -37,6 +37,7 @@
 #include "ImageLoader.h"
 #include "Shader.h"
 #include "Program.h"
+#include "FileMonitor.h"
 
 class Shark {
 private:
@@ -174,7 +175,9 @@
 	Window win;
 	XVisualInfo* vi;
 	GLXContext glc;
-	
+
+	FileMonitor fileMonitor;
+	std::vector<WatchedFile> watchedFiles;
 	ImageLoader imageLoader;
 	std::vector<std::shared_ptr<Shader>> shaders;
 	std::shared_ptr<Program> shaderProgram;
--- a/nbproject/configurations.xml	Fri Dec 01 21:02:02 2023 +0100
+++ b/nbproject/configurations.xml	Sat Dec 02 15:02:56 2023 +0100
@@ -2,6 +2,7 @@
 <configurationDescriptor version="100">
   <logicalFolder name="root" displayName="root" projectFiles="true" kind="ROOT">
     <df root="." name="0">
+      <in>FileMonitor.cpp</in>
       <in>ImageLoader.cpp</in>
       <in>Program.cpp</in>
       <in>Shader.cpp</in>
@@ -55,6 +56,10 @@
           <preBuildCommand></preBuildCommand>
         </preBuild>
       </makefileType>
+      <item path="FileMonitor.cpp" ex="false" tool="1" flavor2="0">
+        <ccTool flags="0">
+        </ccTool>
+      </item>
       <item path="ImageLoader.cpp" ex="false" tool="1" flavor2="0">
         <ccTool flags="0">
         </ccTool>