@ -1,88 +0,0 @@
					 | 
				
			||||
--- | 
				
			||||
title: "My First Blog Post" | 
				
			||||
description: "This is a test article." | 
				
			||||
navigation: true | 
				
			||||
date: "2025-03-01" | 
				
			||||
img: "/img/personal-website/login.png" | 
				
			||||
tags: | 
				
			||||
  - C++20 | 
				
			||||
  - GLSL | 
				
			||||
  - Lua | 
				
			||||
  - Software | 
				
			||||
--- | 
				
			||||
 | 
				
			||||
Foobarbazbuz. | 
				
			||||
 | 
				
			||||
## Component Rendering | 
				
			||||
 | 
				
			||||
::ExampleComponent | 
				
			||||
#namedslot | 
				
			||||
This is a Named Slot | 
				
			||||
#default | 
				
			||||
This is the Default Slot | 
				
			||||
:: | 
				
			||||
 | 
				
			||||
### Testing Some Markdown Features | 
				
			||||
 | 
				
			||||
A link: [website-vue](https://github.com/riyyi/website-vue) | 
				
			||||
 | 
				
			||||
A codeblock: | 
				
			||||
```js [file.js]{2} meta-info=val | 
				
			||||
export default () => { | 
				
			||||
  console.log('Code block') | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
int i = 0; | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
```php | 
				
			||||
protected static $router; | 
				
			||||
$path = parse_url($_SERVER['REQUEST_URI'])['path']; | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Inline `hightlight` looks like so. | 
				
			||||
 | 
				
			||||
Inline highlight with language `const code: string = 'highlighted code inline'`{lang="ts"} like this. | 
				
			||||
 | 
				
			||||
- An | 
				
			||||
- Unordered | 
				
			||||
- List | 
				
			||||
 | 
				
			||||
## Heading With No Subheading | 
				
			||||
 | 
				
			||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a tempor | 
				
			||||
dolor. Nullam mattis sapien vel finibus dignissim. Etiam et diam ultrices, | 
				
			||||
aliquam enim nec, commodo sem. Cras ut faucibus risus. Suspendisse vel faucibus | 
				
			||||
ipsum. Duis vel orci nec arcu porttitor fermentum eu quis est. Phasellus elit | 
				
			||||
odio, elementum ac placerat at, feugiat sit amet sapien. | 
				
			||||
 | 
				
			||||
Vestibulum dapibus pharetra metus. Integer volutpat lacus nec enim euismod, id | 
				
			||||
dignissim felis rhoncus. Cras commodo tempus turpis, eu vehicula mi lacinia | 
				
			||||
eget. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per | 
				
			||||
inceptos himenaeos. Integer rhoncus dolor ut dolor gravida vestibulum. Ut | 
				
			||||
dignissim orci a ornare ullamcorper. Nam facilisis mauris sit amet nunc | 
				
			||||
fermentum pretium. Nullam in nisi at risus luctus viverra. Mauris non congue | 
				
			||||
dolor, vel finibus lectus. Nam quis leo pretium, sodales augue et, dictum sem. | 
				
			||||
 | 
				
			||||
Curabitur velit ante, imperdiet in eros eu, iaculis gravida risus. Nullam ut | 
				
			||||
feugiat eros, viverra vehicula sem. Aliquam finibus mi magna, eu fringilla | 
				
			||||
tellus ullamcorper vel. Fusce eget auctor mi. Mauris venenatis pellentesque | 
				
			||||
arcu. Nam ac diam sem. Nulla suscipit sed risus non vehicula. Cras molestie | 
				
			||||
lectus et tincidunt tempor. Ut tempus lorem id augue semper convallis. Aliquam | 
				
			||||
vel dui quis dolor cursus faucibus eget a mi. Curabitur tempus justo diam, a | 
				
			||||
facilisis ligula tincidunt viverra. Donec sodales quis dolor at dignissim. | 
				
			||||
Nullam placerat vitae urna quis bibendum. | 
				
			||||
 | 
				
			||||
Integer a magna a velit bibendum mollis. Donec lobortis molestie diam at rutrum. | 
				
			||||
Nunc viverra gravida metus at facilisis. Nulla sit amet erat sodales, commodo | 
				
			||||
elit sed, ultrices magna. Aliquam vel purus fringilla, gravida enim quis, | 
				
			||||
aliquam augue. Duis interdum et erat nec feugiat. Praesent vitae lacinia leo, | 
				
			||||
non suscipit purus. | 
				
			||||
 | 
				
			||||
Aenean massa magna, imperdiet id ex quis, mollis auctor lectus. Cras velit | 
				
			||||
nulla, volutpat eu neque id, semper venenatis urna. Integer in blandit ex, non | 
				
			||||
scelerisque nisi. Ut sagittis tincidunt enim at volutpat. Sed hendrerit metus ac | 
				
			||||
interdum laoreet. In rutrum turpis in nulla vulputate suscipit. Nunc dictum nisl | 
				
			||||
id magna laoreet dapibus. | 
				
			||||
@ -0,0 +1,432 @@
					 | 
				
			||||
--- | 
				
			||||
title: "Inferno" | 
				
			||||
description: "An open-source game engine." | 
				
			||||
navigation: true | 
				
			||||
date: "2019-12-10" | 
				
			||||
img: "/img/inferno/preview.png" | 
				
			||||
tags: | 
				
			||||
  - C++20 | 
				
			||||
  - GLSL | 
				
			||||
  - Lua | 
				
			||||
  - CMake | 
				
			||||
  - Software | 
				
			||||
--- | 
				
			||||
 | 
				
			||||
<small>An open-source game engine.<br> | 
				
			||||
Repository at | 
				
			||||
[GitHub](https://github.com/riyyi/inferno){target="_blank"}, | 
				
			||||
[GitLab](https://gitlab.com/riyyi/inferno){target="_blank"} or | 
				
			||||
[Gitea](https://git.riyyi.com/riyyi/inferno){target="_blank"}. | 
				
			||||
</small> | 
				
			||||
 | 
				
			||||
Video games have always been a hobby of mine, but what if I could combine my | 
				
			||||
profession with this hobby? Then you get this project, a game engine! | 
				
			||||
 | 
				
			||||
This open-source game engine is written in C++20, GLSL and Lua with the | 
				
			||||
libraries EnTT, glad, GLFW, GLM, sol3 and stb, using the build tool CMake. | 
				
			||||
 | 
				
			||||
## Abstractions | 
				
			||||
 | 
				
			||||
Making a game engine is not an easy task, if the project has any chance of | 
				
			||||
success it needs a solid foundation. Therefor, the main focus of the project | 
				
			||||
thus far has been to work on solid, easy-to-use abstractions. The additional | 
				
			||||
benefit of this approach is that I learned a lot about software design in the | 
				
			||||
process. | 
				
			||||
 | 
				
			||||
### Events | 
				
			||||
 | 
				
			||||
The library used for window and context creation and receiving input is GLFW, | 
				
			||||
this is abstracted away in a "Window" wrapper class. This "Window" is created in | 
				
			||||
the "Application" class, which also binds the callback function for event | 
				
			||||
handling. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
// Make it more readable | 
				
			||||
#define NF_BIND_EVENT(f) std::bind(&f, this, std::placeholders::_1) | 
				
			||||
 | 
				
			||||
m_window = std::make_unique<Window>(); | 
				
			||||
m_window->setEventCallback(NF_BIND_EVENT(Application::onEvent)); | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
The event callback is stored in a std::function. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
inline void setEventCallback(const std::function<void(Event&)>& callback) { m_eventCallback = callback; } | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Inside the "Window" wrapper, we first associate the wrapper to the internal "GLFWwindow" object. | 
				
			||||
```cpp | 
				
			||||
glfwSetWindowUserPointer(m_window, this); | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Then, it can set the appropriate callback functions. In this example, we set the | 
				
			||||
position of the mouse. It gets the "Window" wrapper from the association, then | 
				
			||||
creates an event object, which is passed on to the callback function. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
// Mouse position callback | 
				
			||||
glfwSetCursorPosCallback(m_window, [](GLFWwindow* window, double xPos, double yPos) { | 
				
			||||
	Window& w = *(Window*)glfwGetWindowUserPointer(window); | 
				
			||||
 | 
				
			||||
	MousePositionEvent event(xPos, yPos); | 
				
			||||
	w.m_eventCallback(event); | 
				
			||||
}); | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
All we have to do to handle these incoming events is create an event dispatcher | 
				
			||||
and call dispatch. The dispatcher simply checks if the current event is of the | 
				
			||||
same type as the provided event and calls the function if this is the case. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
void Application::onEvent(Event& e) | 
				
			||||
{ | 
				
			||||
	EventDispatcher dispatcher(e); | 
				
			||||
	dispatcher.dispatch<MousePositionEvent>(NF_BIND_EVENT(Application::onMousePosition)); | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
### Logging | 
				
			||||
 | 
				
			||||
I wanted logging to work with both `printf` and `std::cout` style logging. To | 
				
			||||
make logging fast and easy to use, it also has to be compatible with | 
				
			||||
user-defined types. The class hierarchy looks like the following: | 
				
			||||
 | 
				
			||||
``` | 
				
			||||
. | 
				
			||||
└── LogStream | 
				
			||||
	└── BufferedLogStream | 
				
			||||
		├── DebugLogStream | 
				
			||||
		└── StringLogStream | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
"LogStream" is an abstract class with a pure virtual function named "write", to | 
				
			||||
be used as the interface. We need both variants of the write function in order | 
				
			||||
to also print extended ASCII characters. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
class LogStream { | 
				
			||||
public: | 
				
			||||
	virtual void write(const char* characters, int length) const = 0; | 
				
			||||
	virtual void write(const unsigned char* characters, int length) const = 0; | 
				
			||||
}; | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
To extend the functionality of the "LogStream" class, we override the bitwise | 
				
			||||
left shift `<<` operator. Support for user-defined types is added in the same way. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
const LogStream& operator<<(const LogStream& stream, const std::string& value) | 
				
			||||
{ | 
				
			||||
	stream.write(value.c_str(), value.length()); | 
				
			||||
	return stream; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
const LogStream& operator<<(const LogStream& stream, bool value) | 
				
			||||
{ | 
				
			||||
	return stream << (value ? "true": "false"); | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
In order to keep I/O performance high, logging is done in a buffered manner. | 
				
			||||
When a "BufferedLogStream" is created it allocates an array of 128 bytes in | 
				
			||||
length on the stack, which you can then write to. If the buffer size is | 
				
			||||
exceeded, the buffer grows automatically to the required size in chunks of 128, | 
				
			||||
this time on the heap. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
// Append to buffer | 
				
			||||
memcpy(buffer() + m_count, characters, length); | 
				
			||||
 | 
				
			||||
// Buffer is increased in chunks of 128 bytes | 
				
			||||
size_t newCapacity = (m_count + bytes + BUFFER_SIZE - 1) & ~(BUFFER_SIZE - 1); | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
The "DebugLogStream" class is used to display messages. It has two variables to | 
				
			||||
configure this behavior, "newline" and "type", to indicate if a newline should | 
				
			||||
be printed and the color of the message. When the object goes out of scope, it | 
				
			||||
will print everything that was added to the buffer in one go. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
fwrite(buffer(), 1, count(), stdout); | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Now we get to the actual usage of the system. Using helper functions, printing | 
				
			||||
colored and formatted messages is easy. Each helper function also has a | 
				
			||||
non-newline variant as newlines are optional. They simply create a | 
				
			||||
"DebugLogStream" and return it. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
DebugLogStream dbg(bool newline); | 
				
			||||
DebugLogStream info(bool newline); | 
				
			||||
DebugLogStream warn(bool newline); | 
				
			||||
DebugLogStream danger(bool newline); | 
				
			||||
DebugLogStream success(bool newline); | 
				
			||||
 | 
				
			||||
dbg() << "Hello World! " << 2.5 << " " << true; | 
				
			||||
info() << "This will be printed in blue."; | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
The printf style logging is more complicated than the previous use case. This is | 
				
			||||
because the function needs to be able to process any number of arguments and of | 
				
			||||
any type, this is accomplished using a template parameter pack. We use recursion | 
				
			||||
to loop through all the parameters in this pack, with an overload for when there | 
				
			||||
is no parameter expansion. Because the "DebugLogStream" is used for printing, | 
				
			||||
all the type overloads are available to us, so we don't have to specify the type | 
				
			||||
like usual with `printf`. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
void dbgln(Log type, bool newline); | 
				
			||||
void dbgln(Log type, bool newline, const char* format); | 
				
			||||
 | 
				
			||||
template<typename T, typename... P> | 
				
			||||
void dbgln(Log type, bool newline, const char* format, T value, P&&... parameters) | 
				
			||||
{ | 
				
			||||
	std::string_view view { format }; | 
				
			||||
 | 
				
			||||
	for(uint32_t i = 0; format[i] != '\0'; i++) { | 
				
			||||
 | 
				
			||||
		if (format[i] == '{' && format[i + 1] == '}') { | 
				
			||||
			DebugLogStream(type, false) << view.substr(0, i) << value; | 
				
			||||
			dbgln(type, newline, format + i + 2, parameters...); | 
				
			||||
			return; | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
This style of logging also has a bunch of helper functions to make using them | 
				
			||||
quick and easy. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
template<typename... P> void dbgln(const char* format, P&&... parameters) { dbgln(Log::None, true, format, std::forward<P>(parameters)...); } | 
				
			||||
template<typename... P> void infoln(const char* format, P&&... parameters) { dbgln(Log::Info, true, format, std::forward<P>(parameters)...); } | 
				
			||||
template<typename... P> void warnln(const char* format, P&&... parameters) { dbgln(Log::Warn, true, format, std::forward<P>(parameters)...); } | 
				
			||||
template<typename... P> void dangerln(const char* format, P&&... parameters) { dbgln(Log::Danger, true, format, std::forward<P>(parameters)...); } | 
				
			||||
template<typename... P> void successln(const char* format, P&&... parameters) { dbgln(Log::Success, true, format, std::forward<P>(parameters)...); } | 
				
			||||
 | 
				
			||||
dbgln("Hello {}, print anything! {} {}", "World", 2.5, true); | 
				
			||||
dangerln("This will print in {}.", "red"); | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Finally, "StringLogStream" is used to convert any supported type into a | 
				
			||||
std::string. This is achieved by simply converting the buffer in | 
				
			||||
"BufferedLogStream" to a string and setting the provided string to it when the | 
				
			||||
object goes out of scope. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
StringLogStream str(std::string* fill); | 
				
			||||
 | 
				
			||||
std::string result; | 
				
			||||
str(&result) << "Add " << "anything " << 2.5 << " " << false; | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
### Shaders | 
				
			||||
 | 
				
			||||
"Shader" functionality is split into two classes, "Shader" and "ShaderManager". | 
				
			||||
The manager does exactly what the name would suggest, it manages the resources. | 
				
			||||
Using convenient functions you can `add`, `load`, `check existence`, `remove` | 
				
			||||
shaders. The shaders get stored in a hash table (hash map), with the key being | 
				
			||||
its name and the value a `std::shared_ptr` to the shader object. Adding anything | 
				
			||||
to the manager that has already been loaded will simply return the existing | 
				
			||||
instance, to prevent duplication. The other pairs "Texture/TextureManager", | 
				
			||||
"Font/FontManager" and "Gltf/GltfManager" are structured similarly. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
void add(const std::string& name, const std::shared_ptr<Shader>& shader); | 
				
			||||
std::shared_ptr<Shader> load(const std::string& name); | 
				
			||||
std::shared_ptr<Shader> load(const std::string& vertexSource, | 
				
			||||
                             const std::string& fragmentSource); | 
				
			||||
bool exists(const std::string& name); | 
				
			||||
 | 
				
			||||
void remove(const std::string& name); | 
				
			||||
void remove(const std::shared_ptr<Shader>& shader); | 
				
			||||
 | 
				
			||||
std::unordered_map<std::string, std::shared_ptr<Shader>> m_shaderList; | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
To construct a "Shader", only a name needs to be provided. It will then load, | 
				
			||||
compile and link both the vertex and fragment shader files. Any errors like | 
				
			||||
files not existing or GLSL syntax errors will be printed to the console. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
// Get file contents | 
				
			||||
std::string vertexSrc = File::read(name + ".vert"); | 
				
			||||
std::string fragmentSrc = File::read(name + ".frag"); | 
				
			||||
 | 
				
			||||
// Compile shaders | 
				
			||||
uint32_t vertexID = compileShader(GL_VERTEX_SHADER, vertexSrc.c_str()); | 
				
			||||
uint32_t fragmentID = compileShader(GL_FRAGMENT_SHADER, fragmentSrc.c_str()); | 
				
			||||
 | 
				
			||||
// Link shaders | 
				
			||||
if (vertexID > 0 && fragmentID > 0) { | 
				
			||||
	m_id = linkShader(vertexID, fragmentID); | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
An uniform is a global "Shader" variable, to set uniform data, there are | 
				
			||||
functions for the most common used data types. ASSERT is a wrapper for `dbgln`, | 
				
			||||
which can print any registered data types, explained in the | 
				
			||||
[Logging](#logging) | 
				
			||||
section. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
int32_t findUniform(const std::string& name) const; | 
				
			||||
 | 
				
			||||
void setInt(const std::string& name, int value); | 
				
			||||
void setInt(const std::string& name, int* values, uint32_t count); | 
				
			||||
void setFloat(const std::string& name, float value) const; | 
				
			||||
void setFloat(const std::string& name, float v1, float v2, float v3, float v4) const; | 
				
			||||
void setFloat(const std::string& name, glm::vec2 value) const; | 
				
			||||
void setFloat(const std::string& name, glm::vec3 value) const; | 
				
			||||
void setFloat(const std::string& name, glm::vec4 value) const; | 
				
			||||
void setFloat(const std::string& name, glm::mat3 matrix) const; | 
				
			||||
void setFloat(const std::string& name, glm::mat4 matrix) const; | 
				
			||||
 | 
				
			||||
void bind() const; | 
				
			||||
void unbind() const; | 
				
			||||
 | 
				
			||||
int32_t Shader::findUniform(const std::string& name) const | 
				
			||||
{ | 
				
			||||
	int32_t location = glGetUniformLocation(m_id, name.c_str()); | 
				
			||||
	ASSERT(location != -1, "Shader could not find uniform '{}'", name); | 
				
			||||
	return location; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void Shader::setInt(const std::string& name, int value) | 
				
			||||
{ | 
				
			||||
	// Set unifrom int | 
				
			||||
	glUniform1i(findUniform(name), value); | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
In the renderer we only need to do the following. The `the` function in | 
				
			||||
"ShaderManager" is a static function that gets the instance of the singleton. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
m_shader = ShaderManager::the().load("assets/glsl/batch-quad"); | 
				
			||||
 | 
				
			||||
m_shader->bind(); | 
				
			||||
m_shader->setFloat("u_projectionView", cameraProjectionView); | 
				
			||||
m_shader->unbind(); | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
### Buffers | 
				
			||||
 | 
				
			||||
Rendering in OpenGL is done using a set of two buffers, the vertex buffer and | 
				
			||||
the index buffer. The vertex buffer is used to store geometry data, in the | 
				
			||||
format of points. From these points, you can construct triangles, which can | 
				
			||||
actually be rendered. The constructed triangles are stored as indexes to points | 
				
			||||
in the index buffer, the simplest form of an index buffer of a single triangle | 
				
			||||
looks like the following: `[0, 1, 2]`. When you have these two buffers set up | 
				
			||||
correctly, you can draw a triangle. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
void RenderCommand::drawIndexed(const VertexArray& vertexArray, uint32_t indexCount) | 
				
			||||
{ | 
				
			||||
	uint32_t count = indexCount ? indexCount : vertexArray.getIndexBuffer()->getCount(); | 
				
			||||
	glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr); | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
To create a vertex buffer a lot of manual setup is needed. This includes offset | 
				
			||||
calculation, which is very prone to errors, so these steps are abstracted away | 
				
			||||
in the "VertexBuffer" class. A "VertexBuffer" object stores a "BufferLayout", | 
				
			||||
which stores a `std::vector` of "BufferElements". "BufferElements" are what | 
				
			||||
OpenGL calls vertex attributes, which allows us to specify any input we want, | 
				
			||||
because of this we also have to specify how the data should be interpreted. The | 
				
			||||
"BufferElement" objects hold the variables `type`, the types `size`, `offset` in | 
				
			||||
the buffer and if the data is normalized, these can be accessed via getter | 
				
			||||
functions. Now for the fun part of vertex buffers, the "BufferLayout", via a | 
				
			||||
`std::initializer_list` they can be constructed very easily. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
BufferLayout::BufferLayout(const std::initializer_list<BufferElement>& elements) | 
				
			||||
	: m_elements(elements) | 
				
			||||
{ | 
				
			||||
	calculateOffsetsAndStride(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void BufferLayout::calculateOffsetsAndStride() | 
				
			||||
{ | 
				
			||||
    m_stride = 0; | 
				
			||||
	for (auto& element : m_elements) { | 
				
			||||
		element.setOffset(m_stride); | 
				
			||||
		m_stride += element.getSize(); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Because of OpenGL's C-like API, rendering requires manual binding of the vertex | 
				
			||||
attribute configurations and vertex buffers, which is a lot of boilerplate. In | 
				
			||||
order to simplify this, a new concept was added called a vertex array object | 
				
			||||
(also known as VAO), which is actually required in OpenGL's core profile. VAOs | 
				
			||||
store the vertex attribute configuration and the associated vertex buffers, they | 
				
			||||
are abstracted away in the "VertexArray" class which stores the "VertexBuffers" | 
				
			||||
and an "IndexBuffer". When adding a "VertexBuffer" to a "VertexArray", it will | 
				
			||||
set up the vertex attribute configuration. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
void VertexArray::addVertexBuffer(std::shared_ptr<VertexBuffer> vertexBuffer) | 
				
			||||
{ | 
				
			||||
	const auto& layout = vertexBuffer->getLayout(); | 
				
			||||
	ASSERT(layout.getElements().size(), "VertexBuffer has no layout"); | 
				
			||||
 | 
				
			||||
	bind(); | 
				
			||||
	vertexBuffer->bind(); | 
				
			||||
 | 
				
			||||
	uint32_t index = 0; | 
				
			||||
	for (const auto& element : layout) { | 
				
			||||
		glEnableVertexAttribArray(index); | 
				
			||||
		glVertexAttribPointer( | 
				
			||||
			index, | 
				
			||||
			element.getTypeCount(), | 
				
			||||
			element.getTypeGL(), | 
				
			||||
			element.getNormalized() ? GL_TRUE : GL_FALSE, | 
				
			||||
			layout.getStride(), | 
				
			||||
			reinterpret_cast<const void*>(element.getOffset())); | 
				
			||||
 | 
				
			||||
		index++; | 
				
			||||
	} | 
				
			||||
	m_vertexBuffers.push_back(std::move(vertexBuffer)); | 
				
			||||
 | 
				
			||||
	unbind(); | 
				
			||||
	vertexBuffer->unbind(); | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
The final usage looks like the following, notice how the layout is created | 
				
			||||
easily using the `std::initializer_list` in the `setLayout` function. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
// Create vertex array | 
				
			||||
m_vertexArray = std::make_shared<VertexArray>(); | 
				
			||||
 | 
				
			||||
// Create vertex buffer | 
				
			||||
auto vertexBuffer = std::make_shared<VertexBuffer>(sizeof(QuadVertex) * vertexCount); | 
				
			||||
vertexBuffer->setLayout({ | 
				
			||||
	{ BufferElementType::Vec3,  "a_position" }, | 
				
			||||
	{ BufferElementType::Vec4,  "a_color" }, | 
				
			||||
	{ BufferElementType::Vec2,  "a_textureCoordinates" }, | 
				
			||||
	{ BufferElementType::Float, "a_textureIndex" }, | 
				
			||||
}); | 
				
			||||
m_vertexArray->addVertexBuffer(vertexBuffer); | 
				
			||||
 | 
				
			||||
uint32_t indices[] { 0, 1, 2, 2, 3, 0 }; | 
				
			||||
 | 
				
			||||
// Create index buffer | 
				
			||||
auto indexBuffer = std::make_shared<IndexBuffer>(indices, sizeof(uint32_t) * indexCount); | 
				
			||||
m_vertexArray->setIndexBuffer(indexBuffer); | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
## Design Structure | 
				
			||||
 | 
				
			||||
Pictured below are all the classes currently in the codebase and the | 
				
			||||
relationships between them. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
## Preview | 
				
			||||
 | 
				
			||||
There isn't much visual to show as of now. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
@ -0,0 +1,63 @@
					 | 
				
			||||
--- | 
				
			||||
title: "Manafiles" | 
				
			||||
description: "Config file and package tracking utility." | 
				
			||||
navigation: false | 
				
			||||
date: "2021-09-04" | 
				
			||||
img: "/img/manafiles-unit-test.png" | 
				
			||||
tags: | 
				
			||||
  - C++20 | 
				
			||||
  - CMake | 
				
			||||
  - Software | 
				
			||||
--- | 
				
			||||
 | 
				
			||||
<small>Config file and package tracking utility.<br> | 
				
			||||
Repository at | 
				
			||||
[GitHub](https://github.com/riyyi/manafiles){target="_blank"}, | 
				
			||||
[GitLab](https://gitlab.com/riyyi/manafiles){target="_blank"} or | 
				
			||||
[Gitea](https://git.riyyi.com/riyyi/manafiles){target="_blank"}. | 
				
			||||
</small> | 
				
			||||
 | 
				
			||||
Written in C++20, using the build tool CMake. | 
				
			||||
 | 
				
			||||
The goal of this project is simple reproducibility of a Linux system, without | 
				
			||||
using symlinks. This is achieved by managing configuration files and keeping | 
				
			||||
track of what packages were installed. | 
				
			||||
 | 
				
			||||
In order to use the same bundle of files for multiple systems, the program | 
				
			||||
allows to specify variables inside of the configuration files. These | 
				
			||||
configuration lines will then get commented or uncommented when pushing the | 
				
			||||
configuration to the system, depending on the value of the variables. The | 
				
			||||
variables that are supported are the `distribution` the `hostname`, the | 
				
			||||
`username`, and the display `session`, which is either X.Org or Wayland. | 
				
			||||
 | 
				
			||||
Below an example of a variable block, where I set the amount of jobs the | 
				
			||||
compiler will use, depending on the hostname, because my desktop has more cores | 
				
			||||
than my laptop. | 
				
			||||
 | 
				
			||||
``` | 
				
			||||
# >>> hostname=arch-desktop | 
				
			||||
MAKEFLAGS="-j8" | 
				
			||||
# <<< | 
				
			||||
# >>> hostname=arch-laptop | 
				
			||||
# MAKEFLAGS="-j4" | 
				
			||||
# <<< | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
List of features: | 
				
			||||
 | 
				
			||||
- Manage dotfiles and system config files. | 
				
			||||
- Selectively comment and uncomment depending on machine configuration. | 
				
			||||
- Store a list of all installed packages. | 
				
			||||
- Install packages from a stored list. | 
				
			||||
- Pattern matching in the config file and cli arguments. | 
				
			||||
- Test suite with unit tests, using my own macros. | 
				
			||||
 | 
				
			||||
Pictured below is the output of running the test suite: | 
				
			||||
 | 
				
			||||
<div class="row"> | 
				
			||||
<div class="col-6"> | 
				
			||||
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
</div> | 
				
			||||
</div> | 
				
			||||
@ -0,0 +1,36 @@
					 | 
				
			||||
--- | 
				
			||||
title: "OpenGL Test" | 
				
			||||
description: "OpenGL test." | 
				
			||||
navigation: false | 
				
			||||
date: "2021-02-20" | 
				
			||||
img: "/img/opengl-test-preview.png" | 
				
			||||
tags: | 
				
			||||
  - C++14 | 
				
			||||
  - GLSL | 
				
			||||
  - CMake | 
				
			||||
  - Software | 
				
			||||
--- | 
				
			||||
 | 
				
			||||
<small>Config file and package tracking utility.<br> | 
				
			||||
Repository at | 
				
			||||
[GitHub](https://github.com/riyyi/opengl-test){target="_blank"}, | 
				
			||||
[GitLab](https://gitlab.com/riyyi/opengl-test){target="_blank"} or | 
				
			||||
[Gitea](https://git.riyyi.com/riyyi/opengl-test){target="_blank"}. | 
				
			||||
</small> | 
				
			||||
 | 
				
			||||
Written in C++ with glad, glfw, glm and stb, using the build tool CMake. | 
				
			||||
Created for the Computer Graphics course at Hogeschool Rotterdam. | 
				
			||||
 | 
				
			||||
Design structure. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
Preview. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
Demo showcasing translation, rotation, scaling and textures working. You can | 
				
			||||
also fly around, which is not shown in the video. | 
				
			||||
 | 
				
			||||
::VideoLazy{:src="https://riyyi.com/media/opengl-test-demo.webm"} | 
				
			||||
:: | 
				
			||||
@ -1,12 +0,0 @@
					 | 
				
			||||
--- | 
				
			||||
title: "My Second Blog Post" | 
				
			||||
description: "This is another article." | 
				
			||||
navigation: false | 
				
			||||
date: "2025-03-02" | 
				
			||||
img: "/img/personal-website/reset-password.png" | 
				
			||||
tags: | 
				
			||||
  - Bash | 
				
			||||
  - Hardware | 
				
			||||
--- | 
				
			||||
 | 
				
			||||
This is another article. | 
				
			||||
@ -0,0 +1,28 @@
					 | 
				
			||||
--- | 
				
			||||
title: "Simple 2D RPG" | 
				
			||||
description: "Simple 2D RPG." | 
				
			||||
navigation: false | 
				
			||||
date: "2015-03-09" | 
				
			||||
img: "/img/simple-2d-rpg.png" | 
				
			||||
tags: | 
				
			||||
  - C++11 | 
				
			||||
  - CMake | 
				
			||||
  - Software | 
				
			||||
--- | 
				
			||||
 | 
				
			||||
<small>Simple 2D RPG.<br> | 
				
			||||
Repository at | 
				
			||||
[GitHub](https://github.com/riyyi/rpg){target="_blank"}, | 
				
			||||
[GitLab](https://gitlab.com/riyyi/rpg){target="_blank"} or | 
				
			||||
[Gitea](https://git.riyyi.com/riyyi/rpg){target="_blank"}. | 
				
			||||
</small> | 
				
			||||
 | 
				
			||||
Written in C++ with SFML and RapidJSON, using the build tool CMake. | 
				
			||||
 | 
				
			||||
This is a very old project, the only things that are implemented are walking and | 
				
			||||
tile collision. Mainly used for exploring the SFML library and Tiled tool for | 
				
			||||
level design. | 
				
			||||
 | 
				
			||||
Preview. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
@ -0,0 +1,65 @@
					 | 
				
			||||
--- | 
				
			||||
title: "Space Walk" | 
				
			||||
description: "Space Walk board game." | 
				
			||||
navigation: false | 
				
			||||
date: "2021-02-18" | 
				
			||||
img: "/img/space-walk/phase-1.png" | 
				
			||||
tags: | 
				
			||||
  - C++11 | 
				
			||||
  - Software | 
				
			||||
--- | 
				
			||||
 | 
				
			||||
<small>Board game.<br> | 
				
			||||
Repository at | 
				
			||||
[GitHub](https://github.com/riyyi/space-walk){target="_blank"}, | 
				
			||||
[GitLab](https://gitlab.com/riyyi/space-walk){target="_blank"} or | 
				
			||||
[Gitea](https://git.riyyi.com/riyyi/space-walk){target="_blank"}. | 
				
			||||
</small> | 
				
			||||
 | 
				
			||||
This is an implementation of the [Space | 
				
			||||
Walk](https://mancala.fandom.com/wiki/Space_Walk){target="_blank"} board game, | 
				
			||||
written in C++ with ncurses for the UI, built using GNU Make. Created for the | 
				
			||||
C++ course at Hogeschool Rotterdam. | 
				
			||||
 | 
				
			||||
UML design. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
<div class="row"> | 
				
			||||
<div class="col-12 col-lg-6"> | 
				
			||||
 | 
				
			||||
Title screen | 
				
			||||
 | 
				
			||||
 | 
				
			||||
</div> | 
				
			||||
<div class="col-12 col-lg-6"> | 
				
			||||
 | 
				
			||||
Setting player names. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
</div> | 
				
			||||
<div class="col-12 col-lg-6"> | 
				
			||||
 | 
				
			||||
Message box informing the players of the current phase. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
</div> | 
				
			||||
<div class="col-12 col-lg-6"> | 
				
			||||
 | 
				
			||||
Players take turns putting their space ships onto the planets. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
</div> | 
				
			||||
<div class="col-12 col-lg-6"> | 
				
			||||
 | 
				
			||||
Message box informing the players of the current phase. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
</div> | 
				
			||||
<div class="col-12 col-lg-6"> | 
				
			||||
 | 
				
			||||
Players take turns evacuating the planets, where ships that pass the edges of | 
				
			||||
the board will get sucked into black holes (top and bottom square). | 
				
			||||
 | 
				
			||||
 | 
				
			||||
</div> | 
				
			||||
</div> | 
				
			||||
@ -0,0 +1,200 @@
					 | 
				
			||||
--- | 
				
			||||
title: "Utility Library" | 
				
			||||
description: "Utility library." | 
				
			||||
navigation: true | 
				
			||||
date: "2022-08-17" | 
				
			||||
img: "/img/ruc-example-unit-test.png" | 
				
			||||
tags: | 
				
			||||
  - C++20 | 
				
			||||
  - CMake | 
				
			||||
  - Software | 
				
			||||
--- | 
				
			||||
 | 
				
			||||
<small>Utility library.<br> | 
				
			||||
Repository at | 
				
			||||
[GitHub](https://github.com/riyyi/ruc){target="_blank"}, | 
				
			||||
[GitLab](https://gitlab.com/riyyi/ruc){target="_blank"} or | 
				
			||||
[Gitea](https://git.riyyi.com/riyyi/ruc){target="_blank"}. | 
				
			||||
</small> | 
				
			||||
 | 
				
			||||
C++20 utility library without any dependencies, using build tool CMake. | 
				
			||||
 | 
				
			||||
This is an attempt at deduplicating all the commonly used functionality across | 
				
			||||
my projects and create one cohesive style. | 
				
			||||
 | 
				
			||||
## Argument parsing | 
				
			||||
 | 
				
			||||
placeholder | 
				
			||||
 | 
				
			||||
## Formatting library | 
				
			||||
 | 
				
			||||
placeholder | 
				
			||||
 | 
				
			||||
## JSON parsing | 
				
			||||
 | 
				
			||||
This is a full implementation of the JSON | 
				
			||||
[RFC 7159](https://datatracker.ietf.org/doc/html/rfc7159) | 
				
			||||
specification. Created mostly | 
				
			||||
for fun, but also for the convenient API. | 
				
			||||
 | 
				
			||||
First, lets specify some JSON that we want to parse. | 
				
			||||
 | 
				
			||||
```js | 
				
			||||
{ | 
				
			||||
	"window": { | 
				
			||||
		"title": "Inferno", | 
				
			||||
		"width": 1280 | 
				
			||||
		"height": 720, | 
				
			||||
		"fullscreen": "windowed", | 
				
			||||
		"vsync": false, | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Then, define the structs the JSON will get serialized into. | 
				
			||||
```cpp | 
				
			||||
struct WindowProperties { | 
				
			||||
    std::string title { "Inferno" }; | 
				
			||||
    uint32_t width { 1280 }; | 
				
			||||
    uint32_t height { 720 }; | 
				
			||||
    std::string fullscreen { "borderless" }; | 
				
			||||
    bool vsync { true }; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
struct SettingsProperties { | 
				
			||||
    WindowProperties window; | 
				
			||||
}; | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
To deserialize the JSON into the struct defined above, we first need to read the | 
				
			||||
contents of the JSON file. Then we call the parse function on it, which will | 
				
			||||
return an instance of the base class of the JSON library. Calling the get | 
				
			||||
function on this instance will try to convert it to the specified type, using | 
				
			||||
user-declared conversion functions, more on this later. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
ruc::Json object = ruc::Json::parse(ruc::File("assets/settings.json").data()); | 
				
			||||
 | 
				
			||||
if (object.type() != ruc::Json::Type::Object) { | 
				
			||||
    ruc::warn("Settings invalid formatting"); | 
				
			||||
    return false; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
SettingsProperties properties = object.get<SettingsProperties>(); | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Serializing back into JSON is simple: just create an instance of the JSON base | 
				
			||||
class, whose constructor takes in any type. After, call the dump function on it. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
ruc::Json object = properties; | 
				
			||||
 | 
				
			||||
auto file = ruc::File("assets/settings.json"); | 
				
			||||
file.clear(); | 
				
			||||
file.append(object.dump(1, '\t')); | 
				
			||||
file.append("\n"); | 
				
			||||
file.flush(); | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
So how does this work, how are the JSON objects mapped to the C++ instances? The | 
				
			||||
library is using a [clever | 
				
			||||
trick](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html){target="_blank"} | 
				
			||||
with Argument-Dependent Lookup (ADL). "Lookup" refers to name lookup, which is | 
				
			||||
the process a C++ compiler uses to resolve identifiers to their declarations. We | 
				
			||||
let the compiler search for a function of a specific shape, anywhere in the | 
				
			||||
project, that allows the library to find functions declared by the user of that | 
				
			||||
library! What this effectively means is that all a user has to do is declare a | 
				
			||||
`to` and `from` conversion function anywhere in his project and these will get | 
				
			||||
used automatically by the library. | 
				
			||||
 | 
				
			||||
Below the implementation of the window settings. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
void fromJson(const ruc::Json& object, WindowProperties& window) | 
				
			||||
{ | 
				
			||||
	VERIFY(object.type() == ruc::Json::Type::Object); | 
				
			||||
 | 
				
			||||
	if (object.exists("title")) | 
				
			||||
		object.at("title").getTo(window.title); | 
				
			||||
	if (object.exists("width")) | 
				
			||||
		object.at("width").getTo(window.width); | 
				
			||||
	if (object.exists("height")) | 
				
			||||
		object.at("height").getTo(window.height); | 
				
			||||
	if (object.exists("fullscreen")) | 
				
			||||
		object.at("fullscreen").getTo(window.fullscreen); | 
				
			||||
	if (object.exists("vsync")) | 
				
			||||
		object.at("vsync").getTo(window.vsync); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void toJson(ruc::Json& object, const WindowProperties& window) | 
				
			||||
{ | 
				
			||||
	object = ruc::Json { | 
				
			||||
		{ "title", window.title }, | 
				
			||||
		{ "width", window.width }, | 
				
			||||
		{ "height", window.height }, | 
				
			||||
		{ "fullscreen", window.fullscreen }, | 
				
			||||
		{ "vsync", window.vsync }, | 
				
			||||
	}; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void fromJson(const ruc::Json& object, SettingsProperties& settings) | 
				
			||||
{ | 
				
			||||
	VERIFY(object.type() == ruc::Json::Type::Object); | 
				
			||||
 | 
				
			||||
	if (object.exists("window")) | 
				
			||||
		object.at("window").getTo(settings.window); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void toJson(ruc::Json& object, const SettingsProperties& settings) | 
				
			||||
{ | 
				
			||||
	object = ruc::Json { | 
				
			||||
		{ "window", settings.window } | 
				
			||||
	}; | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
## Unit test macros | 
				
			||||
 | 
				
			||||
These are some macros to quickly setup a unit test. The usage is made very | 
				
			||||
simple, as there is no need to setup a main function entrypoint. | 
				
			||||
 | 
				
			||||
To accomplish this, the macro has a trick to declare the unit test inside of a | 
				
			||||
struct and that also has a static variable of that struct type. Because its | 
				
			||||
static it will get allocated on program startup and in the constructor of the | 
				
			||||
struct it will register the unit test function in the `TestSuite` class. | 
				
			||||
 | 
				
			||||
What this effectively means, is that after the CMake configuration is setup, all | 
				
			||||
a user has to do is create a `.cpp` file and put a test inside of it with the | 
				
			||||
`TEST_CASE` macro. | 
				
			||||
 | 
				
			||||
```cpp | 
				
			||||
#include "macro.h" | 
				
			||||
#include "testcase.h" | 
				
			||||
#include "testsuite.h" | 
				
			||||
 | 
				
			||||
TEST_CASE(ExampleUnitTest) | 
				
			||||
{ | 
				
			||||
    // Test a boolean value | 
				
			||||
    EXPECT(true); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
TEST_CASE(ExampleUnitTestTrue) | 
				
			||||
{ | 
				
			||||
    // Test 2 values, true | 
				
			||||
    int leftside = 3; | 
				
			||||
    int rightside = 3; | 
				
			||||
    EXPECT_EQ(leftside, rightside); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
TEST_CASE(ExampleUnitTestFalse) | 
				
			||||
{ | 
				
			||||
    // Test 2 values, false | 
				
			||||
    int leftside = 3; | 
				
			||||
    int rightside = 5; | 
				
			||||
    EXPECT_EQ(leftside, rightside); | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Output of the testcases above: | 
				
			||||
 | 
				
			||||
 | 
				
			||||
| 
		 After Width: | Height: | Size: 582 KiB  | 
| 
		 After Width: | Height: | Size: 776 KiB  | 
| 
		 After Width: | Height: | Size: 238 KiB  | 
| 
		 After Width: | Height: | Size: 72 KiB  | 
| 
		 After Width: | Height: | Size: 74 KiB  | 
| 
		 After Width: | Height: | Size: 34 KiB  | 
| 
		 After Width: | Height: | Size: 59 KiB  | 
| 
		 After Width: | Height: | Size: 23 KiB  | 
| 
		 After Width: | Height: | Size: 21 KiB  | 
| 
		 After Width: | Height: | Size: 21 KiB  | 
| 
		 After Width: | Height: | Size: 24 KiB  | 
| 
		 After Width: | Height: | Size: 10 KiB  | 
| 
		 After Width: | Height: | Size: 15 KiB  | 
| 
		 After Width: | Height: | Size: 461 KiB  | 
@ -0,0 +1,57 @@
					 | 
				
			||||
<template> | 
				
			||||
	<video ref="videoRef" class="w-100" controls> | 
				
			||||
		<source :src="loadedSrc" type="video/webm"> | 
				
			||||
		Your browser does not support the video tag. | 
				
			||||
	</video> | 
				
			||||
</template> | 
				
			||||
 | 
				
			||||
<script setup lang="ts"> | 
				
			||||
import { onMounted, onUnmounted } from "vue" | 
				
			||||
 | 
				
			||||
const props = defineProps({ | 
				
			||||
	src: { | 
				
			||||
		type: String, | 
				
			||||
		default: "" | 
				
			||||
	}, | 
				
			||||
}); | 
				
			||||
 | 
				
			||||
const loadedSrc = ref<string>(); | 
				
			||||
const refinedSrc = computed(() => { | 
				
			||||
	return getPublicPath(props.src); | 
				
			||||
}); | 
				
			||||
 | 
				
			||||
const videoRef = ref<HTMLVideoElement | null>(null) | 
				
			||||
 | 
				
			||||
function lazyLoadVideos() { | 
				
			||||
	const video = videoRef.value; | 
				
			||||
	if (!video || !isInViewport(video)) return; | 
				
			||||
 | 
				
			||||
	removeEvents(); | 
				
			||||
	loadedSrc.value = refinedSrc.value; | 
				
			||||
	video.load(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function removeEvents() { | 
				
			||||
	window.removeEventListener("scroll", lazyLoadVideos); | 
				
			||||
	window.removeEventListener('resize', lazyLoadVideos); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
onMounted(() => { | 
				
			||||
	setTimeout(() => { | 
				
			||||
		window.addEventListener("scroll", lazyLoadVideos); | 
				
			||||
		window.addEventListener("resize", lazyLoadVideos); | 
				
			||||
		lazyLoadVideos(); // trigger immediately | 
				
			||||
	}, 500); | 
				
			||||
}); | 
				
			||||
 | 
				
			||||
onUnmounted(() => { | 
				
			||||
	removeEvents(); | 
				
			||||
}); | 
				
			||||
</script> | 
				
			||||
 | 
				
			||||
<!-- | 
				
			||||
	 Usage: | 
				
			||||
 | 
				
			||||
	::VideoLazy{:src="/img/path-to-video.webm"} | 
				
			||||
	:: | 
				
			||||
--> | 
				
			||||