Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74d571c10f | ||
|
|
9bbbb80e75 | ||
|
|
f610edf22c | ||
|
|
5aa314e40e | ||
|
|
07fde286c4 | ||
|
|
d76464d4f3 | ||
|
|
d033523e88 | ||
|
|
1fe48cfed8 | ||
|
|
48d768d53b | ||
|
|
71de01045e | ||
|
|
a3400f401c | ||
|
|
aebff49be9 |
@@ -50,11 +50,13 @@ Reference: [[https://nuxt.com/docs/getting-started/deployment#client-side-only-r
|
||||
|
||||
* Todo
|
||||
|
||||
- [ ] dark-mode
|
||||
- [ ] persistence in localstorage
|
||||
- [X] dark-mode
|
||||
- [X] persistence in localstorage
|
||||
- [X] <h> tag scroll fix
|
||||
- [ ] pull request? https://github.com/nuxt-modules/mdc/tree/main/src/runtime/components/prose
|
||||
- [X] table of contents: https://github.com/hanyujie2002/nuxt-toc/blob/main/src/runtime/components/TableOfContents.vue
|
||||
- [ ] for mobile via dropdown?
|
||||
- [X] nuke: bootstrap-vue-next
|
||||
- [ ] update readme: bun -> npm, primevue -> bootstrap
|
||||
- [X] update readme: bun -> npm, primevue -> bootstrap
|
||||
- [X] better popup code copy button
|
||||
- [ ] sitemap.xml
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { defineContentConfig, defineCollection, z } from '@nuxt/content'
|
||||
import { asSitemapCollection } from "@nuxtjs/sitemap/content"
|
||||
|
||||
// Lookup resolved from path: rootDir/content
|
||||
// see: https://github.com/nuxt/content/issues/3161
|
||||
|
||||
export default defineContentConfig({
|
||||
collections: {
|
||||
content: defineCollection({
|
||||
content: defineCollection(
|
||||
asSitemapCollection({
|
||||
type: "page",
|
||||
source: "**/*.md",
|
||||
schema: z.object({
|
||||
date: z.date(),
|
||||
img: z.string(),
|
||||
sub: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
title: "Bank ATM"
|
||||
description: "Bank ATM."
|
||||
navigation: true
|
||||
date: "2018-03-18"
|
||||
img: "/img/bank-atm/arduino-fritzing.png"
|
||||
tags:
|
||||
- C++14
|
||||
- Qt4
|
||||
- Software
|
||||
- Hardware
|
||||
---
|
||||
|
||||
<small>Bank ATM.<br>
|
||||
|
||||
The goal of this project was to create a banking system, including an ATM
|
||||
client. It was created for one of the courses (project 3) at Hogeschool
|
||||
Rotterdam and was written in C++14 and Qt4.
|
||||
|
||||
The
|
||||
[slides](https://docs.google.com/presentation/d/1W0L4e7r0Vqp5Qp3RaNcMhqrt1gkLSrU4T6heOZz_IqQ){target="_blank"}
|
||||
for the presentation that I gave at Hogeschool Rotterdam.
|
||||
|
||||
## Product demo
|
||||
|
||||
Korte video waarin de meeste functionaliteit van de bank ATM wordt getoond.
|
||||
|
||||
::VideoLazy{:src="/img/bank-atm/demo.webm"}
|
||||
::
|
||||
|
||||
## Overzicht componenten
|
||||
|
||||
- Bank
|
||||
- MySQL server
|
||||
- Native applicatie
|
||||
- Arduino Nano
|
||||
- RFID-RC522
|
||||
- Matrix membrane 4x4 keypad
|
||||
- USB kabel voor seriële communicatie
|
||||
|
||||

|
||||
|
||||
## Overzicht systeem
|
||||
|
||||
De applicatie bestaat uit 3 threads, welke allemaal een unieke functie hebben.
|
||||
De main thread bevat de GUI. De database thread voert alle queries uit en slaat
|
||||
deze data op. En als laatst de serial thread, deze thread bevat een listener die
|
||||
alle seriële data ontvangt. Afhankelijk van wat de serial thread aan user input
|
||||
ontvangt zal de SerialCommand class "commando's" uitvoeren die de GUI en
|
||||
database thread gebruiken.
|
||||
|
||||

|
||||
|
||||
## Database ERD
|
||||
|
||||
Hieronder is de ER (Entity-relationship) diagram te vinden.
|
||||
|
||||

|
||||
|
||||
## Database ontwerp
|
||||
|
||||
- MySQL server
|
||||
- Lokaal opgeslagen
|
||||
|
||||
Hieronder is de EER (Enhanced entity-relationship) diagram te vinden.
|
||||
|
||||

|
||||
|
||||
## Arduino - fritzing
|
||||
|
||||
- Arduino Nano
|
||||
- RFID-RC522
|
||||
- Matrix membrane 4x4 keypad
|
||||
|
||||

|
||||
|
||||
## GUI
|
||||
|
||||
- Native applicatie
|
||||
- Geschreven in C++14 en Qt4
|
||||
- Cross platform (build tool: qmake)
|
||||
- Multithreaded
|
||||
|
||||
Ten alle tijden kan de sessie worden afgebroken door de pas uit de pinautomaat
|
||||
te verwijderen, de interface wordt daarna naar de inlogpagina doorgestuurd. Alle
|
||||
velden waar de gebruiker gegevens kan invoeren, hebben de mogelijkheid voor het
|
||||
weergeven van foutmeldingen.
|
||||
|
||||
Hieronder zijn schermvoorbeelden te vinden.
|
||||
|
||||

|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: "blaze lisp"
|
||||
description: "An implementation of the MAL (Make a Lisp) project."
|
||||
navigation: false
|
||||
date: "2023-03-18"
|
||||
img: "/img/mal-steps.png"
|
||||
tags:
|
||||
- C++20
|
||||
- CMake
|
||||
- Software
|
||||
---
|
||||
|
||||
<small>An implementation of the MAL (Make a Lisp) project.<br>
|
||||
Repository at
|
||||
[GitHub](https://github.com/riyyi/blaze){target="_blank"},
|
||||
[GitLab](https://gitlab.com/riyyi/blaze){target="_blank"} or
|
||||
[Gitea](https://git.riyyi.com/riyyi/blaze){target="_blank"}.
|
||||
</small>
|
||||
|
||||
After having implemented the JSON serializer for my [utility
|
||||
library](/articles/utility-library), I became interested in interpreters. The
|
||||
utility library just has the `tokenization` step of an interpreter, so my tought
|
||||
was, what's next? That's right, `parsing`.
|
||||
|
||||
Having used Emacs quite a bit, I wasn't shy of Lisp. Then, I came across the MAL
|
||||
([Make a Lisp](https://github.com/kanaka/mal){target="_blank"}) project, and
|
||||
decided to look into it. The project lays out the implementation steps of making
|
||||
a Lisp programming language in 10 or so steps and also provides you with unit
|
||||
tests that have to pass to proceed to the next step. There are also a bunch of
|
||||
reference implementations to refer to, in case you get stuck.
|
||||
|
||||
Feeling like implementing a fully interpreted language was a bit of an
|
||||
undertaking and realizing that in a Lisp `parsing` and `interpreting` is
|
||||
basically combined into one step, it seemed achievable and I decided to give the
|
||||
MAL project a go.
|
||||
|
||||
The final architechture that has to be implemented is pictured below.
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-9 col-xl-6">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
I managed to do a full implementation, including `variables`, `if` statements,
|
||||
`loops`, `functions`, `try/catch` exceptions, `lists`, `arrays`, `hash-maps` and
|
||||
even `macros`.
|
||||
|
||||
With the REPL you can try things out and play around easily.
|
||||
|
||||
|
||||
```
|
||||
./repl
|
||||
Blaze [C++]
|
||||
user>
|
||||
```
|
||||
|
||||
```elisp
|
||||
user> 123
|
||||
123
|
||||
user> (+ 1 2.5)
|
||||
3.5
|
||||
user> (- 10 (- 5 3))
|
||||
8
|
||||
user> "hello world"
|
||||
"hello world"
|
||||
user> (= 2 3)
|
||||
true
|
||||
user> (if (> 2 4) "yes" "no")
|
||||
"no"
|
||||
user> (count (list 3 6 2 9))
|
||||
4
|
||||
user> (count '(3 6 2 9)) ; list shorthand
|
||||
4
|
||||
user> (list? [2.1 nil "text"]) ; array / vector
|
||||
false
|
||||
user> {:a (+ 7 8)} ; hash-map
|
||||
{"a" 15}
|
||||
user> (def! mul (fn* [x y] (* x y))) ; defining a function
|
||||
#<user-function>(0x5f35f38815d0)
|
||||
user> (mul 4 5)
|
||||
20
|
||||
```
|
||||
|
||||
This project was really interesting and the language is quite powerful, powerful
|
||||
enough to accomplish self-hosting! This means that an interpreter (or rather,
|
||||
eval+apply) _for_ this language, can be written _in_ the language.
|
||||
|
||||
If you're curious about reading more examples, a good place is probably the
|
||||
testsuite for the project, those have a bunch of unit tests and are available
|
||||
[here](https://github.com/kanaka/mal/tree/master/impls/tests).
|
||||
@@ -0,0 +1,160 @@
|
||||
---
|
||||
title: "Dijkstra's Shortest Path"
|
||||
description: "Dijkstra's shortest path."
|
||||
navigation: false
|
||||
date: "2021-03-02 02:31:20"
|
||||
img: "/img/algorithms/dijkstras-shortest-path.png"
|
||||
tags:
|
||||
- Algorithm
|
||||
---
|
||||
|
||||
Implementation of the shortest path algorithm, invented by Dijkstra.
|
||||
|
||||

|
||||
|
||||
```python
|
||||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
# List of all the cities
|
||||
cities = [
|
||||
"Ab'Dendriel",
|
||||
"Ankrahmun",
|
||||
"Carlin",
|
||||
"Darashia",
|
||||
"Edron",
|
||||
"Liberty Bay",
|
||||
"Port Hope",
|
||||
"Roshamuul",
|
||||
"Oramond",
|
||||
"Svargrond",
|
||||
"Thais",
|
||||
"Venore",
|
||||
"Yalahar",
|
||||
"Krailos",
|
||||
"Issavi",
|
||||
"Cormaya",
|
||||
]
|
||||
|
||||
# Possible boat fares
|
||||
fares = [
|
||||
[None, None, 80, None, 70, None, None, None, None, None, 130, 90, 160, None, None, None], # Ab'Dendriel
|
||||
[None, None, None, 100, 160, 90, 80, None, None, None, None, 150, 230, None, None, None], # Ankrahmun
|
||||
[ 80, None, None, None, 110, None, None, None, None, 110, 110, 130, 185, None, None, None], # Carlin
|
||||
[None, 100, None, None, None, 200, 180, None, None, None, None, 60, 210, 110, 130, None], # Darashia
|
||||
[ 70, 160, 110, None, None, 170, 150, None, None, None, 160, 40, None, 100, None, 20], # Edron
|
||||
[None, 90, None, 200, 170, None, 50, None, None, None, 180, 180, 275, None, None, None], # Liberty Bay
|
||||
[None, 110, None, 180, 150, 50, None, None, None, None, 160, 160, 260, None, None, None], # Port Hope
|
||||
[None, None, None, None, None, None, None, None, None, None, 210, None, None, None, None, None], # Roshamuul
|
||||
[None, None, None, None, 110, None, 200, None, None, None, 150, 130, None, 60, 120, None], # Oramond
|
||||
[None, None, 110, None, None, None, None, None, None, None, 180, 150, None, None, None, None], # Svargrond
|
||||
[ 130, None, 110, None, 160, 180, 160, 210, 150, 180, None, 170, 200, None, None, None], # Thais
|
||||
[ 90, 150, 130, 60, 40, 180, 160, None, None, 150, 170, None, 185, 110, 130, None], # Venore
|
||||
[ 160, 230, 185, 210, None, 275, 260, None, None, None, 200, 185, None, None, None, None], # Yalahar
|
||||
[None, None, None, 110, 100, None, None, None, 60, None, None, 110, None, None, 70, None], # Krailos
|
||||
[None, None, None, 80, None, None, None, None, 100, None, None, 80, None, 80, None, None], # Issavi
|
||||
[None, None, None, None, 20, None, None, None, None, None, None, None, None, None, None, None], # Cormaya
|
||||
]
|
||||
|
||||
# Say phrase
|
||||
def say(string):
|
||||
time.sleep(0.2)
|
||||
os.system('xdotool key Return')
|
||||
os.system('xdotool keydown Alt')
|
||||
os.system('xdotool type "' + string + '"')
|
||||
os.system('xdotool keyup Alt')
|
||||
os.system('xdotool key Return')
|
||||
|
||||
# Say fare
|
||||
def typeFare(city):
|
||||
say("hi")
|
||||
time.sleep(1.0)
|
||||
say(city)
|
||||
say("yes")
|
||||
|
||||
def dijkstraShortestPath(sourceVertex):
|
||||
explored = []
|
||||
unexplored = []
|
||||
|
||||
# Setup vertices
|
||||
for i, fare in enumerate(fares[sourceVertex]):
|
||||
unexplored.append([i, None, None])
|
||||
|
||||
# Set the source vertex distance to 0
|
||||
if i == sourceVertex:
|
||||
unexplored[-1][1] = 0
|
||||
|
||||
while len(unexplored) > 0:
|
||||
# Select the closest vertex
|
||||
closest = None
|
||||
distance = None
|
||||
for i, vertex in enumerate(unexplored):
|
||||
if (distance == None and vertex[1] != None) or \
|
||||
(vertex[1] != None and vertex[1] < distance):
|
||||
closest = i
|
||||
distance = vertex[1]
|
||||
|
||||
# Move the closest vertex to explored
|
||||
explored.append(unexplored.pop(closest))
|
||||
ID = explored[-1][0]
|
||||
|
||||
# Explore this vertex
|
||||
for i, vertex in enumerate(unexplored):
|
||||
|
||||
# Is linked
|
||||
if fares[ID][vertex[0]] != None:
|
||||
|
||||
# If it hasnt been set before
|
||||
if vertex[1] == None:
|
||||
vertex[1] = fares[ID][vertex[0]] + explored[-1][1]
|
||||
vertex[2] = ID
|
||||
# Or is cheaper
|
||||
elif vertex[1] > fares[ID][vertex[0]] + explored[-1][1]:
|
||||
vertex[1] = fares[ID][vertex[0]] + explored[-1][1]
|
||||
vertex[2] = ID
|
||||
|
||||
return explored
|
||||
|
||||
def dijkstraTraversePath(sourceVertex, targetVertex, explored):
|
||||
find = targetVertex
|
||||
path = [targetVertex]
|
||||
while find != sourceVertex:
|
||||
for i, vertex in enumerate(explored):
|
||||
if vertex[0] == find:
|
||||
find = vertex[2]
|
||||
path.append(vertex[2])
|
||||
break
|
||||
path.pop()
|
||||
path.reverse()
|
||||
return path
|
||||
|
||||
def main():
|
||||
locations = '\n'.join(cities)
|
||||
current = os.popen('echo "' + locations + '" | rofi -dmenu -i -p "Where are you now?"').read().rstrip()
|
||||
destination = os.popen('echo "' + locations + '" | rofi -dmenu -i -p "Where are you going?"').read().rstrip()
|
||||
|
||||
if not current or not destination: return
|
||||
|
||||
currentIndex = cities.index(current)
|
||||
destinationIndex = cities.index(destination)
|
||||
|
||||
# If a direct route is possible
|
||||
if fares[currentIndex][destinationIndex] != None:
|
||||
typeFare(destination)
|
||||
return
|
||||
|
||||
# Calculate possible non-direct route
|
||||
data = dijkstraShortestPath(currentIndex)
|
||||
path = dijkstraTraversePath(currentIndex, destinationIndex, data)
|
||||
|
||||
for index in path:
|
||||
typeFare(cities[index])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
Source:<br>
|
||||
[Dijkstra Algorithm - Example (YouTube)](https://www.youtube.com/watch?v=JcN_nq1EAr4){ target=_blank }
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: "Elevator Floor Module"
|
||||
description: "Elevator floor module."
|
||||
navigation: false
|
||||
date: "2017-12-02"
|
||||
img: "/img/elevator-floor-module/backside-view-final.jpg"
|
||||
tags:
|
||||
- Hardware
|
||||
---
|
||||
|
||||
The goal of this project was to create a working miniature elevator. Each member
|
||||
of the team made his own level, which we would stack on top of each other in the
|
||||
end. The engine room was a combined effort of the team and it would talk to all
|
||||
the levels via I2C. This was my first project at Hogeschool Rotterdam.
|
||||
|
||||
Het houten frame van de verdieping.
|
||||
|
||||

|
||||
|
||||
Hardware schema van alle sensoren, de Arduino Mega representeerd de motorkamer.
|
||||
|
||||

|
||||
|
||||
Het bedieningspaneel aan de voorkant van de lift. De 2 pushbuttons worden
|
||||
gebruikt om de lift op te roepen. Het segment display geeft de huidige locatie
|
||||
van de liftkooi weer en het ledje aan de bovenkant simmuleert de deur.
|
||||
|
||||

|
||||
|
||||
De sensoren worden aangestuurt met een Arduino Uno.
|
||||
De uiteindelijke aansluiting ziet er als volgt uit.
|
||||
|
||||

|
||||
|
||||
Alle lift verdiepingen van ons project groepje opgestapeld tot een toren.
|
||||
|
||||

|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
title: "My First Blog Post"
|
||||
description: "This is a test article."
|
||||
navigation: true
|
||||
sub: "C++20, GLSL, Lua"
|
||||
img: "/img/personal-website/login.png"
|
||||
date: "2025-03-01"
|
||||
---
|
||||
|
||||
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,170 @@
|
||||
---
|
||||
title: "FizzBuzz"
|
||||
description: "FizzBuzz."
|
||||
navigation: false
|
||||
date: "2021-03-02 02:31:02"
|
||||
tags:
|
||||
- Algorithm
|
||||
---
|
||||
|
||||
Implementation of the classic FizzBuzz interview question, done in two ways.
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
void calculate()
|
||||
{
|
||||
for (int i = 1; i < 101; i++) {
|
||||
if (i % 15 == 0) {
|
||||
std::cout << "FizzBuzz" << std::endl;
|
||||
}
|
||||
else if (i % 5 == 0) {
|
||||
std::cout << "Buzz" << std::endl;
|
||||
}
|
||||
else if (i % 3 == 0) {
|
||||
std::cout << "Fizz" << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cout << i << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pattern()
|
||||
{
|
||||
const char* pattern[] = {
|
||||
"%d\n",
|
||||
"%d\n",
|
||||
"Fizz\n",
|
||||
"%d\n",
|
||||
"Buzz\n",
|
||||
"Fizz\n",
|
||||
"%d\n",
|
||||
"%d\n",
|
||||
"Fizz\n",
|
||||
"Buzz\n",
|
||||
"%d\n",
|
||||
"Fizz\n",
|
||||
"%d\n",
|
||||
"%d\n",
|
||||
"FizzBuzz\n",
|
||||
};
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
printf(pattern[i % 15], i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
calculate();
|
||||
|
||||
pattern();
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Output (x2):
|
||||
```
|
||||
1
|
||||
2
|
||||
Fizz
|
||||
4
|
||||
Buzz
|
||||
Fizz
|
||||
7
|
||||
8
|
||||
Fizz
|
||||
Buzz
|
||||
11
|
||||
Fizz
|
||||
13
|
||||
14
|
||||
FizzBuzz
|
||||
16
|
||||
17
|
||||
Fizz
|
||||
19
|
||||
Buzz
|
||||
Fizz
|
||||
22
|
||||
23
|
||||
Fizz
|
||||
Buzz
|
||||
26
|
||||
Fizz
|
||||
28
|
||||
29
|
||||
FizzBuzz
|
||||
31
|
||||
32
|
||||
Fizz
|
||||
34
|
||||
Buzz
|
||||
Fizz
|
||||
37
|
||||
38
|
||||
Fizz
|
||||
Buzz
|
||||
41
|
||||
Fizz
|
||||
43
|
||||
44
|
||||
FizzBuzz
|
||||
46
|
||||
47
|
||||
Fizz
|
||||
49
|
||||
Buzz
|
||||
Fizz
|
||||
52
|
||||
53
|
||||
Fizz
|
||||
Buzz
|
||||
56
|
||||
Fizz
|
||||
58
|
||||
59
|
||||
FizzBuzz
|
||||
61
|
||||
62
|
||||
Fizz
|
||||
64
|
||||
Buzz
|
||||
Fizz
|
||||
67
|
||||
68
|
||||
Fizz
|
||||
Buzz
|
||||
71
|
||||
Fizz
|
||||
73
|
||||
74
|
||||
FizzBuzz
|
||||
76
|
||||
77
|
||||
Fizz
|
||||
79
|
||||
Buzz
|
||||
Fizz
|
||||
82
|
||||
83
|
||||
Fizz
|
||||
Buzz
|
||||
86
|
||||
Fizz
|
||||
88
|
||||
89
|
||||
FizzBuzz
|
||||
91
|
||||
92
|
||||
Fizz
|
||||
94
|
||||
Buzz
|
||||
Fizz
|
||||
97
|
||||
98
|
||||
Fizz
|
||||
Buzz
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: "GarbAGE (Garbage Accurate GameBoy Emulator)"
|
||||
description: "GameBoy Emulator that is not that accurate."
|
||||
navigation: false
|
||||
date: "2022-08-17"
|
||||
img: "/img/garbage.png"
|
||||
tags:
|
||||
- C++20
|
||||
- CMake
|
||||
- Software
|
||||
---
|
||||
|
||||
<small>GarbAGE (Garbage Accurate GameBoy Emulator).<br>
|
||||
Repository at
|
||||
[GitHub](https://github.com/riyyi/garbage){target="_blank"},
|
||||
[GitLab](https://gitlab.com/riyyi/garbage){target="_blank"} or
|
||||
[Gitea](https://git.riyyi.com/riyyi/garbage){target="_blank"}.
|
||||
</small>
|
||||
|
||||
This is an exploration into emulators by me and a friend of mine. The only thing
|
||||
thats really implemented are the CPU opcodes. The cool thing however, is that
|
||||
for the rendering portion of the application we are using my own
|
||||
[game engine](/articles/inferno)! No other libraries were used.
|
||||
|
||||
The simplest game to test is Dr. Mario and even that one doesnt render
|
||||
correctly, but we do get graphics that you can make out, pretty cool!
|
||||
|
||||

|
||||
|
||||
Preview video of the boot sequence and intro of the Dr. Mario game.
|
||||
|
||||
::VideoLazy{:src="/img/garbage-preview.webm"}
|
||||
::
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: "Guinea Pig Cage"
|
||||
description: "Pet guinea pig cage."
|
||||
navigation: true
|
||||
date: "2018-06"
|
||||
img: "/img/guinea-pig-cage/pig.jpg"
|
||||
tags:
|
||||
- Hardware
|
||||
---
|
||||
|
||||
This is a custom designed and build cage for the guinea pigs of my girlfriend.
|
||||
|
||||
## Design
|
||||
|
||||
3D model of the cage.<br>
|
||||
[View](https://www.tinkercad.com/embed/jtFlKzD78Hj)
|
||||
the model in 3D.<br>
|
||||
See the project at
|
||||
[Thinkercad](https://www.tinkercad.com/things/jtFlKzD78Hj).
|
||||
|
||||

|
||||
|
||||
::GridWithImage
|
||||
---
|
||||
rows:
|
||||
- title: Saw
|
||||
- title: File
|
||||
- title: Sandpaper
|
||||
- title: Drill
|
||||
- title: Phillips head screwdriver
|
||||
- title: Paint brush
|
||||
- title: Tape measure
|
||||
---
|
||||
::
|
||||
|
||||
## Shopping list
|
||||
|
||||
::GridWithImage
|
||||
---
|
||||
rows:
|
||||
- title: OSB (244x122cm)
|
||||
src: "/img/guinea-pig-cage/osb.jpg"
|
||||
- title: Pond liner (200x250cm)
|
||||
src: "/img/guinea-pig-cage/pond-liner.jpg"
|
||||
- title: Timberboards (250cm)
|
||||
src: "/img/guinea-pig-cage/timberboard-before.jpg"
|
||||
count: 3
|
||||
- title: Dimensional lumber (270cm)
|
||||
src: "/img/guinea-pig-cage/dimensional-lumber.jpg"
|
||||
count: 4
|
||||
- title: Screws (5cm)
|
||||
src: "/img/guinea-pig-cage/screws-5cm.jpg"
|
||||
count: 50
|
||||
- title: Screws (3.5cm)
|
||||
src: "/img/guinea-pig-cage/screws-3-5cm.jpg"
|
||||
count: 10
|
||||
- title: Wind hook / window buckle
|
||||
src: "/img/guinea-pig-cage/wind-hook.jpg"
|
||||
- title: Metal rings
|
||||
src: "/img/guinea-pig-cage/metal-rings.jpg"
|
||||
count: 9
|
||||
- title: Hinges
|
||||
src: "/img/guinea-pig-cage/hinges.jpg"
|
||||
count: 1
|
||||
- title: Child safe paint (375ml)
|
||||
src: "/img/guinea-pig-cage/paint.jpg"
|
||||
- title: Brick
|
||||
---
|
||||
::
|
||||
|
||||
## Build
|
||||
|
||||
All measurements of the wooden materials.
|
||||
|
||||
| OSB | | Timberboard | | Dimensional lumber |
|
||||
|---------------------------------------------------|---|------------------------------------------------------------|---|------------------------------------------------------------------|
|
||||
|  | |  | |  |
|
||||
|
||||
\* = optional<br>
|
||||
~ = around
|
||||
|
||||
### Construction
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
The timberboard has to be painted on the inward facing side, for wear protection.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -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,154 @@
|
||||
---
|
||||
title: "Invert Binary Tree"
|
||||
description: "Invert binary tree."
|
||||
navigation: false
|
||||
date: "2021-03-02 02:31:09"
|
||||
img: "/img/algorithms/invert-binary-tree.png"
|
||||
tags:
|
||||
- Algorithm
|
||||
---
|
||||
|
||||
Implementation of inverting a binary tree.
|
||||
|
||||

|
||||
|
||||
binarytree.h
|
||||
```cpp
|
||||
#ifndef BINARYTREE_H
|
||||
#define BINARYTREE_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class BinaryTree {
|
||||
public:
|
||||
// Contructors, Destructors
|
||||
|
||||
BinaryTree(uint32_t value, BinaryTree* left = nullptr,
|
||||
BinaryTree* right = nullptr);
|
||||
|
||||
// Functions
|
||||
|
||||
void print();
|
||||
void invert();
|
||||
|
||||
// Getters, Setters
|
||||
|
||||
inline uint32_t value() { return m_value; }
|
||||
|
||||
private:
|
||||
uint32_t m_value;
|
||||
BinaryTree* m_left;
|
||||
BinaryTree* m_right;
|
||||
};
|
||||
|
||||
#endif // BINARYTREE_H
|
||||
```
|
||||
|
||||
binarytree.cpp
|
||||
```c
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "binarytree.h"
|
||||
|
||||
BinaryTree::BinaryTree(uint32_t value, BinaryTree *left, BinaryTree *right)
|
||||
: m_value(value), m_left(left), m_right(right) {}
|
||||
|
||||
void BinaryTree::print()
|
||||
{
|
||||
// Skip nodes without children
|
||||
if (!m_left && !m_right) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Value
|
||||
std::cout << std::setw(2) << m_value << " -> ";
|
||||
// Left
|
||||
std::cout << std::setw(2) << (m_left ? std::to_string(m_left->value()) : "") << " | ";
|
||||
// Right
|
||||
std::cout << (m_right ? std::to_string(m_right->value()) : "") << std::endl;
|
||||
|
||||
// Recursion
|
||||
if (m_left) {
|
||||
m_left->print();
|
||||
}
|
||||
if (m_right) {
|
||||
m_right->print();
|
||||
}
|
||||
}
|
||||
|
||||
void BinaryTree::invert()
|
||||
{
|
||||
// Swap
|
||||
BinaryTree* tmp = m_left;
|
||||
m_left = m_right;
|
||||
m_right = tmp;
|
||||
|
||||
// Recursion
|
||||
if (m_left) {
|
||||
m_left->invert();
|
||||
}
|
||||
if (m_right) {
|
||||
m_right->invert();
|
||||
}
|
||||
}
|
||||
```
|
||||
main.cpp
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
#include "binarytree.h"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
BinaryTree n3(3);
|
||||
BinaryTree n9(9);
|
||||
BinaryTree n13(13);
|
||||
|
||||
BinaryTree n4(4, &n3);
|
||||
BinaryTree n6(6);
|
||||
BinaryTree n8(8, nullptr, &n9);
|
||||
BinaryTree n12(12, nullptr, &n13);
|
||||
|
||||
BinaryTree n5(5, &n4, &n6);
|
||||
BinaryTree n10(10, &n8, &n12);
|
||||
|
||||
BinaryTree n7(7, &n5, &n10);
|
||||
|
||||
std::cout << std::endl << "Binary Tree:" << std::endl;
|
||||
n7.print();
|
||||
|
||||
n7.invert();
|
||||
|
||||
std::cout << std::endl << "Binary Tree Inverted:" << std::endl;
|
||||
n7.print();
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Binary Tree:
|
||||
7 -> 5 | 10
|
||||
5 -> 4 | 6
|
||||
4 -> 3 |
|
||||
10 -> 8 | 12
|
||||
8 -> | 9
|
||||
12 -> | 13
|
||||
|
||||
Binary Tree Inverted:
|
||||
7 -> 10 | 5
|
||||
10 -> 12 | 8
|
||||
12 -> 13 |
|
||||
8 -> 9 |
|
||||
5 -> 6 | 4
|
||||
4 -> | 3
|
||||
```
|
||||
|
||||
Source:<br>
|
||||
[Binary Search Trees in Java (YouTube)](https://www.youtube.com/watch?v=Qa5r8Wsda70){ target=_blank }
|
||||
@@ -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="/img/opengl-test-demo.webm"}
|
||||
::
|
||||
@@ -2,16 +2,20 @@
|
||||
title: "Personal Website"
|
||||
description: "An open-source content management system, used for this website."
|
||||
navigation: false
|
||||
sub: "PHP 7, MySQL, jQuery"
|
||||
date: "2021-03-03"
|
||||
img: "/img/personal-website/admin-menu.png"
|
||||
date: "2025-03-03"
|
||||
tags:
|
||||
- PHP 7
|
||||
- MySQL
|
||||
- jQuery
|
||||
- Software
|
||||
---
|
||||
|
||||
<small>Open-source content management system.<br>
|
||||
Repository at
|
||||
[GitHub](https://github.com/riyyi/website),
|
||||
[GitLab](https://gitlab.com/riyyi/website) or
|
||||
[Gitea](https://git.riyyi.com/riyyi/website).
|
||||
[GitHub](https://github.com/riyyi/website){target="_blank"},
|
||||
[GitLab](https://gitlab.com/riyyi/website){target="_blank"} or
|
||||
[Gitea](https://git.riyyi.com/riyyi/website){target="_blank"}.
|
||||
</small>
|
||||
|
||||
This is the CMS that is used for this website! It's written in PHP 7, MySQL and
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
title: "My Second Blog Post"
|
||||
description: "This is another article."
|
||||
navigation: false
|
||||
sub: "Bash"
|
||||
img: "/img/personal-website/reset-password.png"
|
||||
date: "2025-03-02"
|
||||
---
|
||||
|
||||
This is another article.
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: "Sieve of Eratosthenes"
|
||||
description: "Sieve of Eratosthenes."
|
||||
navigation: false
|
||||
date: "2021-03-02 02:31:16"
|
||||
img: "/img/algorithms/sieve-of-eratosthenes.gif"
|
||||
tags:
|
||||
- Algorithm
|
||||
---
|
||||
|
||||
Implementation of the ancient sieve of Eratosthenes math algorithm, used for
|
||||
finding prime numbers up to a given limit.
|
||||
|
||||

|
||||
|
||||
```c
|
||||
#include <stdbool.h> // bool
|
||||
#include <stdio.h> // printf
|
||||
#include <string.h> // memset
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int limit = 10000;
|
||||
int calculate = limit * 20;
|
||||
|
||||
// Create an array and set all to true
|
||||
bool numbers[calculate];
|
||||
memset(&numbers, true, sizeof(bool) * calculate);
|
||||
|
||||
// Loop through all the numbers
|
||||
for (int i = 2; i < calculate; i++) {
|
||||
|
||||
// Already crossed out numbers don't have to be checked
|
||||
if (numbers[i]) {
|
||||
// Cross out all the numbers that can't be a prime, which are multiples of itself
|
||||
for (int j = i * i; j < calculate; j += i) {
|
||||
numbers[j] = false;
|
||||
}
|
||||
|
||||
// Once you exceed the calculate range, you can exit the loop
|
||||
if (i * i > calculate) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int sum = 0;
|
||||
int counter = 1;
|
||||
// Get the sum of the first 10000 primes
|
||||
for (int i = 2; i < calculate; i++) {
|
||||
if (numbers[i]) {
|
||||
sum += i;
|
||||
|
||||
if (counter >= limit) {
|
||||
break;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
printf("sum of first %d primes is: %d\n", counter, sum);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Source:<br>
|
||||
[https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes){ target=_blank }
|
||||
@@ -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,80 @@
|
||||
---
|
||||
title: "Smart Outlet"
|
||||
description: "Home automation smart outlet."
|
||||
navigation: false
|
||||
date: "2018-10-31"
|
||||
img: "/img/smart-outlet/result.jpg"
|
||||
tags:
|
||||
- Hardware
|
||||
---
|
||||
|
||||
This page describes my version of a smart socket for home automation.
|
||||
The device can be toggled via the button on the enclosure or a wireless signal.
|
||||
|
||||
## Build
|
||||
|
||||
Pictured below is a scetch of the concept of the project.<br>
|
||||
As well as an electrical circuit.
|
||||
|
||||

|
||||
|
||||
The MDF enclosure is cut out with a laser cutter. The base of the design is
|
||||
generated via
|
||||
[boxes.py](https://www.festi.info/boxes.py/ClosedBox).
|
||||
Holes have been added to the base so that the cable,
|
||||
socket, LED and button can be mounted.
|
||||
|
||||

|
||||
|
||||
The button is printed with a 3D printer, the bottom part of the button is
|
||||
extended by 2mm on each side (4mm in total), so that it stays in place.
|
||||
|
||||

|
||||
|
||||
This is the final electrical circuit that was used, which is made in Fritzing.
|
||||
All hardware components (minus the power supply) are incorporated in this
|
||||
circuit. A red LED is used as an example appliance that can be toggled.
|
||||
|
||||

|
||||
|
||||
Pictured below is a model of the design in Tinkercad, the button will be printed
|
||||
in 3D and the housing will be cut out with the laser cutter. The black cylinder
|
||||
represents the power cable.
|
||||
|
||||
Front view.
|
||||
|
||||

|
||||
|
||||
Side view.
|
||||
|
||||

|
||||
|
||||
Back view.
|
||||
|
||||

|
||||
|
||||
Box cut out.
|
||||
|
||||

|
||||
|
||||
3D printed button.
|
||||
|
||||

|
||||
|
||||
Overview of all the electronics.
|
||||
|
||||

|
||||
|
||||
The electronics mounted in the box.
|
||||
|
||||

|
||||
|
||||
Completed assembly.
|
||||
|
||||

|
||||
|
||||
Domoticz is used as a controller, which completes the smart outlet project. View
|
||||
the demo below.
|
||||
|
||||
::VideoLazy{:src="/img/smart-outlet/demo.webm"}
|
||||
::
|
||||
@@ -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,83 @@
|
||||
---
|
||||
title: "Xbox 360 Controller Type-C Mod"
|
||||
description: "Mod to change the Xbox 360 wired controller to Type-C."
|
||||
navigation: false
|
||||
date: "2022-11-28"
|
||||
img: "/img/type-c-mod/controller-after.jpg"
|
||||
tags:
|
||||
- Hardware
|
||||
---
|
||||
|
||||
<small>Mod to change the Xbox 360 wired controller to Type-C.<br>
|
||||
|
||||
## Result
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Tools
|
||||
|
||||
::GridWithImage
|
||||
---
|
||||
rows:
|
||||
- title: Soldering iron
|
||||
- title: Wire stripper
|
||||
src: "/img/type-c-mod/wire-stripper.jpg"
|
||||
- title: Round file
|
||||
src: "/img/type-c-mod/round-file.jpg"
|
||||
- title: Nail file
|
||||
src: "/img/type-c-mod/nail-file.jpg"
|
||||
---
|
||||
::
|
||||
|
||||
## Shopping list
|
||||
|
||||
::GridWithImage
|
||||
---
|
||||
rows:
|
||||
- title: Solder
|
||||
- title: 3D printed bezel
|
||||
src: "/img/type-c-mod/printed-bezel.jpg"
|
||||
- title: USB Type-C female socket
|
||||
src: "/img/type-c-mod/port.jpg"
|
||||
---
|
||||
::
|
||||
|
||||
Unfortunately, the original design was a little fragile at the round part that
|
||||
would slide into the housing and it would snap off. I tweaked the design by
|
||||
moving the USB port up by 1mm and reinforcing it by attaching it on the sides,
|
||||
as there was space to do so. The 3D bezel design was taken from here:
|
||||
[thingiverse.com/thing:3066354](https://thingiverse.com/thing:3066354).<br>
|
||||
The new edited design:
|
||||
[thingiverse.com/thing:5664077](https://thingiverse.com/thing:5664077).<br>
|
||||
This is the USB-C port I used:
|
||||
[aliexpress.com/item/1005002328403673.html](https://aliexpress.com/item/1005002328403673.html)
|
||||
|
||||
## Build
|
||||
|
||||
Unmodified, the original cable hole is a little too narrow to fit a Type-C port.
|
||||
|
||||

|
||||
|
||||
I used the nail file to slightly widen the hole, this is what it looks like afterwards.
|
||||
|
||||

|
||||
|
||||
Initially, my plan was to replace the original connector with a male pin header
|
||||
and dupont connectors, but my soldering iron couldn't get hot enough to melt the
|
||||
solder of the thick pins. So instead, I repurposed the original wires.
|
||||
|
||||

|
||||
|
||||
I used a third hand to help keep the port in place while soldering.
|
||||
|
||||

|
||||
|
||||
Finished soldering.
|
||||
|
||||

|
||||
|
||||
Test fitting everything together.
|
||||
|
||||

|
||||
@@ -0,0 +1,317 @@
|
||||
---
|
||||
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
|
||||
|
||||
This is a simple argument parsing feature, with the attempt of making it simpler
|
||||
to use than getopt. All you have to do is specify the variables you want to use,
|
||||
then add the options and arguments on the `ArgParser` object and finally call
|
||||
the `parse` function.
|
||||
|
||||
```cpp
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ruc/argparser.h"
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
bool verbose = false;
|
||||
std::vector<std::string> targets {};
|
||||
|
||||
ruc::ArgParser argParser;
|
||||
argParser.addOption(verbose, 'v', "verbose", nullptr, nullptr);
|
||||
argParser.addArgument(targets, "targets", nullptr, nullptr, ruc::ArgParser::Required::No);
|
||||
argParser.parse(argc, argv);
|
||||
|
||||
// Do some work here..
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
The `nullptr`'s in the above example were going to be used for automatic help
|
||||
string generation, but that isn't implemented yet.
|
||||
|
||||
## Formatting library
|
||||
|
||||
This is basically a partial copy of the `fmt` library, from before that became
|
||||
part of the C++20 standard and I didn't want to use an additional dependency in
|
||||
my projects, because that is less fun!
|
||||
|
||||
The part of `fmt` that is implement is the
|
||||
"[mini-language](https://fmt.dev/11.1/syntax/#format-specification-mini-language)",
|
||||
which is a very convenient API.
|
||||
|
||||
```cpp
|
||||
#include <string>
|
||||
|
||||
#include "ruc/format/format.h"
|
||||
#include "ruc/format/print.h"
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
std::string fmt = format(
|
||||
R"(
|
||||
number {}
|
||||
string {}
|
||||
bool {}j
|
||||
double {}
|
||||
double {:.2} with 2 precision
|
||||
)", 123, "this is a string", true, 456.789, 3.14159265359);
|
||||
print("{}\n", fmt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Which results the the output.
|
||||
|
||||
```
|
||||
number 123
|
||||
string this is a string
|
||||
bool true
|
||||
double 456.789000
|
||||
double 3.14 with 2 precision
|
||||
```
|
||||
|
||||
The implementation consists of 2 major parts, the parsing of the format string
|
||||
and the `format` and `print` functions. The parsing part isn't that complex, so
|
||||
wont be discussed here. The other secion is interesting, however.
|
||||
|
||||
The functions are implemented with variadic arguments using type erasure, this
|
||||
improves both compile time and binary size significantly. What it also does, is
|
||||
allow the library to work on types that are specified by the user and are
|
||||
therefor not part of the library.
|
||||
|
||||
The formatter implements all the primitives and some of the STL types, it can be
|
||||
extended by the user. The basic use-case is to specify to just the `format`
|
||||
function, but the `parser` function can also be overwritten.
|
||||
|
||||
```cpp
|
||||
// In the header we are extending the existing formatter for vectors,
|
||||
// so we take advantage of their nice formatting automatically
|
||||
template<>
|
||||
struct ruc::format::Formatter<glm::vec4> : Formatter<std::vector<float>> {
|
||||
void format(Builder& builder, glm::vec4 value) const;
|
||||
};
|
||||
|
||||
// Then in the implementation, we implement the format functon
|
||||
void ruc::format::Formatter<glm::vec4>::format(Builder& builder, glm::vec4 value) const
|
||||
{
|
||||
return Formatter<std::vector<float>>::format(builder, { value.x, value.y, value.z, value.w });
|
||||
}
|
||||
```
|
||||
|
||||
Users can even extend formatters based on their own formats. In this example we
|
||||
are extending the vector (`glm::vec4`) formatter, so we can also print matrices
|
||||
(`glm::mat4`).
|
||||
|
||||
```cpp
|
||||
template<>
|
||||
struct ruc::format::Formatter<glm::mat4> : Formatter<glm::vec4> {
|
||||
void format(Builder& builder, glm::mat4 value) const;
|
||||
};
|
||||
|
||||
void ruc::format::Formatter<glm::mat4>::format(Builder& builder, glm::mat4 value) const
|
||||
{
|
||||
builder.putString("mat4 ");
|
||||
Formatter<glm::vec4>::format(builder, value[0]);
|
||||
builder.putString("\n ");
|
||||
Formatter<glm::vec4>::format(builder, value[1]);
|
||||
builder.putString("\n ");
|
||||
Formatter<glm::vec4>::format(builder, value[2]);
|
||||
builder.putString("\n ");
|
||||
return Formatter<glm::vec4>::format(builder, value[3]);
|
||||
}
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
@@ -15,7 +15,7 @@ export default defineNuxtConfig({
|
||||
},
|
||||
langs: [
|
||||
// https://github.com/shikijs/shiki/blob/main/packages/langs/package.json
|
||||
"c", "cpp", "css", "html", "js", "json", "lua", "md", "mdc", "php", "shell", "ts", "vue", "yaml"
|
||||
"c", "cpp", "css", "elisp", "html", "js", "json", "lua", "md", "mdc", "php", "python", "shell", "ts", "vue", "yaml"
|
||||
]
|
||||
},
|
||||
toc: {
|
||||
@@ -36,7 +36,9 @@ export default defineNuxtConfig({
|
||||
public: "../public"
|
||||
},
|
||||
modules: [
|
||||
"@nuxt/content",
|
||||
"@nuxtjs/robots",
|
||||
"@nuxtjs/sitemap",
|
||||
"@nuxt/content", // NOTE: @nuxt/content after robots and sitemap
|
||||
"@nuxt/eslint",
|
||||
"@pinia/nuxt",
|
||||
"pinia-plugin-persistedstate/nuxt",
|
||||
@@ -52,6 +54,11 @@ export default defineNuxtConfig({
|
||||
secure: process.env.NODE_ENV !== "development" // only send over HTTPS
|
||||
}
|
||||
},
|
||||
routeRules: {
|
||||
// Dont add to the sitemap.xml
|
||||
"/__nuxt_content/**": { robots: false },
|
||||
},
|
||||
site: { url: "https://riyyi.com", name: "Personal Website" },
|
||||
srcDir: "src/",
|
||||
ssr: false,
|
||||
typescript: {
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
"@iconify-json/fa-solid": "^1.2.1",
|
||||
"@nuxt/content": "^3.4.0",
|
||||
"@nuxt/eslint": "^1.2.0",
|
||||
"@nuxtjs/robots": "^5.2.9",
|
||||
"@nuxtjs/sitemap": "^7.2.10",
|
||||
"@types/bootstrap": "^5.2.10",
|
||||
"@vue/language-server": "^2.1.10",
|
||||
"@vue/typescript-plugin": "^2.1.10",
|
||||
@@ -1642,6 +1644,136 @@
|
||||
"vfile": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxtjs/robots": {
|
||||
"version": "5.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@nuxtjs/robots/-/robots-5.2.9.tgz",
|
||||
"integrity": "sha512-JJykE1llIByw8vqhrn4abSPyAcEFnfVOkIaD2/XLwkhNeZHzAQT2/p+ZhqwtaSMyWq8pnW1y0ei7gQYdCqOu+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^3.16.2",
|
||||
"consola": "^3.4.2",
|
||||
"defu": "^6.1.4",
|
||||
"nuxt-site-config": "^3.1.9",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.1.0",
|
||||
"sirv": "^3.0.1",
|
||||
"std-env": "^3.9.0",
|
||||
"ufo": "^1.5.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxtjs/robots/node_modules/@nuxt/kit": {
|
||||
"version": "3.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.16.2.tgz",
|
||||
"integrity": "sha512-K1SAUo2vweTfudKZzjKsZ5YJoxPLTspR5qz5+G61xtZreLpsdpDYfBseqsIAl5VFLJuszeRpWQ01jP9LfQ6Ksw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"c12": "^3.0.2",
|
||||
"consola": "^3.4.2",
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3",
|
||||
"errx": "^0.1.0",
|
||||
"exsolve": "^1.0.4",
|
||||
"globby": "^14.1.0",
|
||||
"ignore": "^7.0.3",
|
||||
"jiti": "^2.4.2",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.2.0",
|
||||
"mlly": "^1.7.4",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.1.0",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.7.1",
|
||||
"std-env": "^3.8.1",
|
||||
"ufo": "^1.5.4",
|
||||
"unctx": "^2.4.1",
|
||||
"unimport": "^4.1.3",
|
||||
"untyped": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxtjs/sitemap": {
|
||||
"version": "7.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@nuxtjs/sitemap/-/sitemap-7.2.10.tgz",
|
||||
"integrity": "sha512-7w1Ys2XIE/QVTJn5dbt2p/hrmDoGO9Ay1S3o6LI5M/MDRzKPvnXi5ByRUcc7Sfa3LUpLad5/w4IOQ4lyxhq9Hw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nuxt/devtools-kit": "^2.3.2",
|
||||
"@nuxt/kit": "^3.16.2",
|
||||
"chalk": "^5.4.1",
|
||||
"defu": "^6.1.4",
|
||||
"h3-compression": "^0.3.2",
|
||||
"nuxt-site-config": "^3.1.9",
|
||||
"ofetch": "^1.4.1",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.1.0",
|
||||
"radix3": "^1.1.2",
|
||||
"semver": "^7.7.1",
|
||||
"sirv": "^3.0.1",
|
||||
"ufo": "^1.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxtjs/sitemap/node_modules/@nuxt/kit": {
|
||||
"version": "3.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.16.2.tgz",
|
||||
"integrity": "sha512-K1SAUo2vweTfudKZzjKsZ5YJoxPLTspR5qz5+G61xtZreLpsdpDYfBseqsIAl5VFLJuszeRpWQ01jP9LfQ6Ksw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"c12": "^3.0.2",
|
||||
"consola": "^3.4.2",
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3",
|
||||
"errx": "^0.1.0",
|
||||
"exsolve": "^1.0.4",
|
||||
"globby": "^14.1.0",
|
||||
"ignore": "^7.0.3",
|
||||
"jiti": "^2.4.2",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.2.0",
|
||||
"mlly": "^1.7.4",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.1.0",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.7.1",
|
||||
"std-env": "^3.8.1",
|
||||
"ufo": "^1.5.4",
|
||||
"unctx": "^2.4.1",
|
||||
"unimport": "^4.1.3",
|
||||
"untyped": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxtjs/sitemap/node_modules/chalk": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
||||
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-parser/binding-linux-x64-gnu": {
|
||||
"version": "0.56.5",
|
||||
"cpu": [
|
||||
@@ -5873,6 +6005,19 @@
|
||||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/h3-compression": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/h3-compression/-/h3-compression-0.3.2.tgz",
|
||||
"integrity": "sha512-B+yCKyDRnO0BXSfjAP4tCXJgJwmnKp3GyH5Yh66mY9KuOCrrGQSPk/gBFG2TgH7OyB/6mvqNZ1X0XNVuy0qRsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/codedredd"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"h3": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/h3/node_modules/cookie-es": {
|
||||
"version": "1.2.2",
|
||||
"dev": true,
|
||||
@@ -8425,6 +8570,110 @@
|
||||
"nuxt-component-meta": "bin/nuxt-component-meta.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-site-config": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/nuxt-site-config/-/nuxt-site-config-3.1.9.tgz",
|
||||
"integrity": "sha512-YB69GX0st8drv1d5xypweseiEWeR22tfGdyVH3U4R+mpUSz8paBx48ArKC6MgV22DKItoQm51LVoapF5pl5bEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^3.16.2",
|
||||
"nuxt-site-config-kit": "3.1.9",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.1.0",
|
||||
"sirv": "^3.0.1",
|
||||
"site-config-stack": "3.1.9",
|
||||
"ufo": "^1.5.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-site-config-kit": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/nuxt-site-config-kit/-/nuxt-site-config-kit-3.1.9.tgz",
|
||||
"integrity": "sha512-bcmpajYJgkNzA0jTq6CmmhKF2wHZUUKeVx/CIGI8lwWuAD81EBUZN0T4iKvVDo54g9UBrUUl8/5GhD65YBBG0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^3.16.2",
|
||||
"pkg-types": "^2.1.0",
|
||||
"site-config-stack": "3.1.9",
|
||||
"std-env": "^3.9.0",
|
||||
"ufo": "^1.5.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-site-config-kit/node_modules/@nuxt/kit": {
|
||||
"version": "3.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.16.2.tgz",
|
||||
"integrity": "sha512-K1SAUo2vweTfudKZzjKsZ5YJoxPLTspR5qz5+G61xtZreLpsdpDYfBseqsIAl5VFLJuszeRpWQ01jP9LfQ6Ksw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"c12": "^3.0.2",
|
||||
"consola": "^3.4.2",
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3",
|
||||
"errx": "^0.1.0",
|
||||
"exsolve": "^1.0.4",
|
||||
"globby": "^14.1.0",
|
||||
"ignore": "^7.0.3",
|
||||
"jiti": "^2.4.2",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.2.0",
|
||||
"mlly": "^1.7.4",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.1.0",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.7.1",
|
||||
"std-env": "^3.8.1",
|
||||
"ufo": "^1.5.4",
|
||||
"unctx": "^2.4.1",
|
||||
"unimport": "^4.1.3",
|
||||
"untyped": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-site-config/node_modules/@nuxt/kit": {
|
||||
"version": "3.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.16.2.tgz",
|
||||
"integrity": "sha512-K1SAUo2vweTfudKZzjKsZ5YJoxPLTspR5qz5+G61xtZreLpsdpDYfBseqsIAl5VFLJuszeRpWQ01jP9LfQ6Ksw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"c12": "^3.0.2",
|
||||
"consola": "^3.4.2",
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3",
|
||||
"errx": "^0.1.0",
|
||||
"exsolve": "^1.0.4",
|
||||
"globby": "^14.1.0",
|
||||
"ignore": "^7.0.3",
|
||||
"jiti": "^2.4.2",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.2.0",
|
||||
"mlly": "^1.7.4",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.1.0",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.7.1",
|
||||
"std-env": "^3.8.1",
|
||||
"ufo": "^1.5.4",
|
||||
"unctx": "^2.4.1",
|
||||
"unimport": "^4.1.3",
|
||||
"untyped": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt/node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"dev": true,
|
||||
@@ -10419,6 +10668,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/site-config-stack": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/site-config-stack/-/site-config-stack-3.1.9.tgz",
|
||||
"integrity": "sha512-ed53+wLi+36SGqidU+YUpl7f1OHClPLmvUJ/aYZny1dCBnXvOsuFottrMkXDIK2N5UaMED9mz8KrRZTk94ARCg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ufo": "^1.5.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3"
|
||||
}
|
||||
},
|
||||
"node_modules/skin-tone": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
@@ -10620,7 +10885,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "3.8.1",
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
|
||||
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/streamx": {
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
"@iconify-json/fa-solid": "^1.2.1",
|
||||
"@nuxt/content": "^3.4.0",
|
||||
"@nuxt/eslint": "^1.2.0",
|
||||
"@nuxtjs/robots": "^5.2.9",
|
||||
"@nuxtjs/sitemap": "^7.2.10",
|
||||
"@types/bootstrap": "^5.2.10",
|
||||
"@vue/language-server": "^2.1.10",
|
||||
"@vue/typescript-plugin": "^2.1.10",
|
||||
|
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 344 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 460 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 386 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 646 KiB |
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 626 KiB |
|
After Width: | Height: | Size: 461 KiB |
|
After Width: | Height: | Size: 510 KiB |
|
After Width: | Height: | Size: 384 KiB |
|
After Width: | Height: | Size: 572 KiB |
|
After Width: | Height: | Size: 584 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 664 KiB |
|
After Width: | Height: | Size: 607 KiB |
|
After Width: | Height: | Size: 436 KiB |
|
After Width: | Height: | Size: 627 KiB |
|
After Width: | Height: | Size: 478 KiB |
|
After Width: | Height: | Size: 479 KiB |
|
After Width: | Height: | Size: 472 KiB |
|
After Width: | Height: | Size: 477 KiB |
|
After Width: | Height: | Size: 558 KiB |
|
After Width: | Height: | Size: 422 KiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 470 KiB |
|
After Width: | Height: | Size: 379 KiB |
|
After Width: | Height: | Size: 431 KiB |
|
After Width: | Height: | Size: 373 KiB |
|
After Width: | Height: | Size: 575 KiB |
|
After Width: | Height: | Size: 609 KiB |
|
After Width: | Height: | Size: 604 KiB |
|
After Width: | Height: | Size: 475 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 605 KiB |
|
After Width: | Height: | Size: 582 KiB |
|
After Width: | Height: | Size: 776 KiB |
|
After Width: | Height: | Size: 405 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: 680 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 379 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 792 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 277 KiB |
|
After Width: | Height: | Size: 112 KiB |