Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74d571c10f | ||
|
|
9bbbb80e75 | ||
|
|
f610edf22c | ||
|
|
5aa314e40e | ||
|
|
07fde286c4 | ||
|
|
d76464d4f3 | ||
|
|
d033523e88 | ||
|
|
1fe48cfed8 | ||
|
|
48d768d53b | ||
|
|
71de01045e | ||
|
|
a3400f401c | ||
|
|
aebff49be9 | ||
|
|
551478d15a | ||
|
|
2c58f39f07 | ||
|
|
232dcf5f14 | ||
|
|
88af6052b3 | ||
|
|
3067d482f3 | ||
|
|
6ac81c61d9 | ||
|
|
64a8aecf25 |
@@ -9,7 +9,7 @@ Website, written in Typescript.
|
|||||||
Install dependencies:
|
Install dependencies:
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
$ bun install
|
$ npm install
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Development Server
|
** Development Server
|
||||||
@@ -17,8 +17,8 @@ $ bun install
|
|||||||
Start the development server on =http://localhost:3000=:
|
Start the development server on =http://localhost:3000=:
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
$ bun run dev
|
$ npm run dev
|
||||||
$ bun run dev -o # automatically open in the browser
|
$ npm run dev -- -o # automatically open in the browser
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Production
|
** Production
|
||||||
@@ -26,14 +26,14 @@ $ bun run dev -o # automatically open in the browser
|
|||||||
Build for production:
|
Build for production:
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
$ bun run build # for server-side rendering or hybrid
|
$ npm run build # for server-side rendering or hybrid
|
||||||
$ bun run generate # for static site generation
|
$ npm run generate # for static site generation
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Locally preview production build:
|
Locally preview production build:
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
$ bun run preview # requires building first!
|
$ npm run preview # requires building first!
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Reference: [[https://nuxt.com/docs/getting-started/deployment#client-side-only-rendering][nuxt/docs/deployment#client-side-only-rendering]]
|
Reference: [[https://nuxt.com/docs/getting-started/deployment#client-side-only-rendering][nuxt/docs/deployment#client-side-only-rendering]]
|
||||||
@@ -43,38 +43,20 @@ Reference: [[https://nuxt.com/docs/getting-started/deployment#client-side-only-r
|
|||||||
- [[https://github.com/vuejs/core][Vue3]] JavaScript framework
|
- [[https://github.com/vuejs/core][Vue3]] JavaScript framework
|
||||||
- [[https://github.com/nuxt/nuxt][Nuxt]] Vue framework
|
- [[https://github.com/nuxt/nuxt][Nuxt]] Vue framework
|
||||||
- [[https://github.com/vuejs/pinia][Pinia]] State management
|
- [[https://github.com/vuejs/pinia][Pinia]] State management
|
||||||
- [[https://github.com/primefaces/primevue][PrimeVue]] Vue component library
|
- [[https://github.com/twbs/bootstrap][Bootstrap5]] CSS library
|
||||||
- [[https://github.com/colinhacks/zod][Zod]] Schema validation
|
- [[https://github.com/colinhacks/zod][Zod]] Schema validation
|
||||||
- [[https://github.com/microsoft/TypeScript][Typescript]] Static types in JavaScript
|
- [[https://github.com/microsoft/TypeScript][Typescript]] Static types in JavaScript
|
||||||
- [[https://github.com/eslint/eslint][ESLint]] Code analyses tool
|
- [[https://github.com/eslint/eslint][ESLint]] Code analyses tool
|
||||||
|
|
||||||
** Package Reproduction
|
* Todo
|
||||||
|
|
||||||
The following bun commands have been run to fill the =package.json=:
|
- [X] dark-mode
|
||||||
|
- [X] persistence in localstorage
|
||||||
#+BEGIN_SRC sh
|
- [X] <h> tag scroll fix
|
||||||
$ bun x nuxi@latest init website
|
- [ ] 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
|
||||||
# Development
|
- [ ] for mobile via dropdown?
|
||||||
$ bun nuxi module add eslint
|
- [X] nuke: bootstrap-vue-next
|
||||||
$ bun install --dev typescript
|
- [X] update readme: bun -> npm, primevue -> bootstrap
|
||||||
$ bun install --dev @vue/language-server
|
- [X] better popup code copy button
|
||||||
$ bun install --dev @vue/typescript-plugin
|
- [ ] sitemap.xml
|
||||||
$ bun install -g typescript
|
|
||||||
$ bun install -g @vue/language-server
|
|
||||||
$ bun install -g @vue/typescript-plugin
|
|
||||||
|
|
||||||
# Pinia
|
|
||||||
$ bun install @pinia/nuxt # also add it to nuxt.config modules!
|
|
||||||
$ bun install pinia-plugin-persistedstate # same as above
|
|
||||||
|
|
||||||
# PrimeVue
|
|
||||||
$ bun install primevue primeicons @primevue/themes @primevue/forms
|
|
||||||
$ bun install --dev @primevue/nuxt-module
|
|
||||||
|
|
||||||
# Zod
|
|
||||||
$ bun install zod
|
|
||||||
|
|
||||||
# UUID
|
|
||||||
$ bun install uuid
|
|
||||||
#+END_SRC
|
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
import { defineContentConfig, defineCollection } from '@nuxt/content'
|
import { defineContentConfig, defineCollection, z } from '@nuxt/content'
|
||||||
|
import { asSitemapCollection } from "@nuxtjs/sitemap/content"
|
||||||
|
|
||||||
// Lookup resolved from path: rootDir/content
|
// Lookup resolved from path: rootDir/content
|
||||||
// see: https://github.com/nuxt/content/issues/3161
|
// see: https://github.com/nuxt/content/issues/3161
|
||||||
|
|
||||||
export default defineContentConfig({
|
export default defineContentConfig({
|
||||||
collections: {
|
collections: {
|
||||||
content: defineCollection({
|
content: defineCollection(
|
||||||
type: "page",
|
asSitemapCollection({
|
||||||
source: "**/*.md"
|
type: "page",
|
||||||
})
|
source: "**/*.md",
|
||||||
}
|
schema: z.object({
|
||||||
})
|
date: z.date(),
|
||||||
|
img: 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,83 +0,0 @@
|
|||||||
---
|
|
||||||
title: "My First Blog Post"
|
|
||||||
description: "This is a test article."
|
|
||||||
navigation: true
|
|
||||||
---
|
|
||||||
|
|
||||||
## Hello World
|
|
||||||
|
|
||||||
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"}
|
||||||
|
::
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
---
|
||||||
|
title: "Personal Website"
|
||||||
|
description: "An open-source content management system, used for this website."
|
||||||
|
navigation: false
|
||||||
|
date: "2021-03-03"
|
||||||
|
img: "/img/personal-website/admin-menu.png"
|
||||||
|
tags:
|
||||||
|
- PHP 7
|
||||||
|
- MySQL
|
||||||
|
- jQuery
|
||||||
|
- Software
|
||||||
|
---
|
||||||
|
|
||||||
|
<small>Open-source content management system.<br>
|
||||||
|
Repository at
|
||||||
|
[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
|
||||||
|
jQuery, with the libraries Klein.php and Mailer.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- PHP 7
|
||||||
|
- Composer
|
||||||
|
- [Klein.php](https://github.com/klein/klein.php)
|
||||||
|
- [Mailer](https://github.com/txthinking/Mailer)
|
||||||
|
- MVC design pattern
|
||||||
|
- MySQL database for storing data
|
||||||
|
- CMS with CRUD functions for managing data
|
||||||
|
- ORM for mapping between PHP classes and data
|
||||||
|
- Login system
|
||||||
|
- Stay logged in using cookies
|
||||||
|
- Forget password with a generated link send using mail
|
||||||
|
- Security mitigations
|
||||||
|
- Password hashing using BCrypt
|
||||||
|
- Per-user cryptographically secure generated salt
|
||||||
|
- SQL injection protection using prepared statements
|
||||||
|
- XSS
|
||||||
|
- Cookies set to 'HttpOnly'
|
||||||
|
- Escape rendered user input using: `htmlentities(ENT_QUOTES | ENT_HTML5, 'UTF-8');`
|
||||||
|
- CSRF
|
||||||
|
- Cookies 'SameSite' set to 'Strict'
|
||||||
|
- Token for each session used in POST/PUT/DELETE requests
|
||||||
|
- Bootstrap
|
||||||
|
- jQuery
|
||||||
|
|
||||||
|
Directory structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── app
|
||||||
|
│ ├── classes
|
||||||
|
│ │ └── <classes>
|
||||||
|
│ ├── controllers
|
||||||
|
│ │ └── <controllers>
|
||||||
|
│ ├── helper.php
|
||||||
|
│ ├── model
|
||||||
|
│ │ └── <models>
|
||||||
|
│ ├── seed.php
|
||||||
|
│ ├── traits
|
||||||
|
│ │ └── Log.php
|
||||||
|
│ └── views
|
||||||
|
│ └── <views>
|
||||||
|
├── composer.json
|
||||||
|
├── config.php
|
||||||
|
├── config.php.example
|
||||||
|
├── public
|
||||||
|
│ ├── index.php
|
||||||
|
│ └── <files>
|
||||||
|
├── route.php
|
||||||
|
├── syncconfig.sh
|
||||||
|
├── syncconfig.sh.example
|
||||||
|
└── sync.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
Pictured below is the EER (Enhanced entity-relationship) diagram of the MySQL database:
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Some of the pages of the CMS.
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
Admin menu.
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
CRUD index page, displaying all the entries of this table, including pagination.
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
CRUD edit page, editing an entry.
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
CRUD show page, show all values of an entry.
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
Login page.
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
Password reset page, showing a flash message.
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
title: "My Second Blog Post"
|
|
||||||
description: "This is another article."
|
|
||||||
---
|
|
||||||
|
|
||||||
# Test!
|
|
||||||
|
|
||||||
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: [
|
langs: [
|
||||||
// https://github.com/shikijs/shiki/blob/main/packages/langs/package.json
|
// 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: {
|
toc: {
|
||||||
@@ -36,7 +36,9 @@ export default defineNuxtConfig({
|
|||||||
public: "../public"
|
public: "../public"
|
||||||
},
|
},
|
||||||
modules: [
|
modules: [
|
||||||
"@nuxt/content",
|
"@nuxtjs/robots",
|
||||||
|
"@nuxtjs/sitemap",
|
||||||
|
"@nuxt/content", // NOTE: @nuxt/content after robots and sitemap
|
||||||
"@nuxt/eslint",
|
"@nuxt/eslint",
|
||||||
"@pinia/nuxt",
|
"@pinia/nuxt",
|
||||||
"pinia-plugin-persistedstate/nuxt",
|
"pinia-plugin-persistedstate/nuxt",
|
||||||
@@ -47,12 +49,16 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
piniaPluginPersistedstate: {
|
piniaPluginPersistedstate: {
|
||||||
debug: process.env.NODE_ENV === "development", // log error to console
|
debug: process.env.NODE_ENV === "development", // log error to console
|
||||||
storage: "cookies",
|
|
||||||
cookieOptions: {
|
cookieOptions: {
|
||||||
sameSite: "lax", // prevent CSRF
|
sameSite: "lax", // prevent CSRF
|
||||||
secure: process.env.NODE_ENV !== "development" // only send over HTTPS
|
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/",
|
srcDir: "src/",
|
||||||
ssr: false,
|
ssr: false,
|
||||||
typescript: {
|
typescript: {
|
||||||
|
|||||||
@@ -22,9 +22,9 @@
|
|||||||
"@iconify-json/fa-solid": "^1.2.1",
|
"@iconify-json/fa-solid": "^1.2.1",
|
||||||
"@nuxt/content": "^3.4.0",
|
"@nuxt/content": "^3.4.0",
|
||||||
"@nuxt/eslint": "^1.2.0",
|
"@nuxt/eslint": "^1.2.0",
|
||||||
|
"@nuxtjs/robots": "^5.2.9",
|
||||||
|
"@nuxtjs/sitemap": "^7.2.10",
|
||||||
"@types/bootstrap": "^5.2.10",
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/bun": "latest",
|
|
||||||
"@vue/compiler-sfc": "^3.5.13",
|
|
||||||
"@vue/language-server": "^2.1.10",
|
"@vue/language-server": "^2.1.10",
|
||||||
"@vue/typescript-plugin": "^2.1.10",
|
"@vue/typescript-plugin": "^2.1.10",
|
||||||
"nuxt": "^3.14.1592",
|
"nuxt": "^3.14.1592",
|
||||||
@@ -1644,6 +1644,136 @@
|
|||||||
"vfile": "^6.0.3"
|
"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": {
|
"node_modules/@oxc-parser/binding-linux-x64-gnu": {
|
||||||
"version": "0.56.5",
|
"version": "0.56.5",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
@@ -2258,16 +2388,6 @@
|
|||||||
"@popperjs/core": "^2.9.2"
|
"@popperjs/core": "^2.9.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/bun": {
|
|
||||||
"version": "1.2.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.8.tgz",
|
|
||||||
"integrity": "sha512-t8L1RvJVUghW5V+M/fL3Thbxcs0HwNsXsnTEBEfEVqGteiJToOlZ/fyOEaR1kZsNqnu+3XA4RI/qmnX4w6+S+w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"bun-types": "1.2.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/debug": {
|
"node_modules/@types/debug": {
|
||||||
"version": "4.1.12",
|
"version": "4.1.12",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2315,6 +2435,8 @@
|
|||||||
"version": "22.13.14",
|
"version": "22.13.14",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.20.0"
|
||||||
}
|
}
|
||||||
@@ -2339,14 +2461,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/ws": {
|
|
||||||
"version": "8.18.0",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.28.0",
|
"version": "8.28.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -3552,15 +3666,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bun-types": {
|
|
||||||
"version": "1.2.7",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*",
|
|
||||||
"@types/ws": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bundle-name": {
|
"node_modules/bundle-name": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -5900,6 +6005,19 @@
|
|||||||
"uncrypto": "^0.1.3"
|
"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": {
|
"node_modules/h3/node_modules/cookie-es": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -8452,6 +8570,110 @@
|
|||||||
"nuxt-component-meta": "bin/nuxt-component-meta.mjs"
|
"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": {
|
"node_modules/nuxt/node_modules/estree-walker": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -10446,6 +10668,22 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/skin-tone": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -10647,7 +10885,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/std-env": {
|
"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"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/streamx": {
|
"node_modules/streamx": {
|
||||||
@@ -11233,7 +11473,9 @@
|
|||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.20.0",
|
"version": "6.20.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/unenv": {
|
"node_modules/unenv": {
|
||||||
"version": "2.0.0-rc.15",
|
"version": "2.0.0-rc.15",
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
"@iconify-json/fa-solid": "^1.2.1",
|
"@iconify-json/fa-solid": "^1.2.1",
|
||||||
"@nuxt/content": "^3.4.0",
|
"@nuxt/content": "^3.4.0",
|
||||||
"@nuxt/eslint": "^1.2.0",
|
"@nuxt/eslint": "^1.2.0",
|
||||||
|
"@nuxtjs/robots": "^5.2.9",
|
||||||
|
"@nuxtjs/sitemap": "^7.2.10",
|
||||||
"@types/bootstrap": "^5.2.10",
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/bun": "latest",
|
|
||||||
"@vue/compiler-sfc": "^3.5.13",
|
|
||||||
"@vue/language-server": "^2.1.10",
|
"@vue/language-server": "^2.1.10",
|
||||||
"@vue/typescript-plugin": "^2.1.10",
|
"@vue/typescript-plugin": "^2.1.10",
|
||||||
"nuxt": "^3.14.1592",
|
"nuxt": "^3.14.1592",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 12 KiB |
|
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: 105 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 51 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 |