Files
www.aklabs.net/2026/05/30/Devlog-20260530/index.html

915 lines
54 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<meta
http-equiv="X-UA-Compatible"
content="ie=edge">
<meta
name="theme-color"
content="#fff"
id="theme-color">
<meta
name="description"
content="AKLabs">
<link
rel="icon"
href="/">
<title>Devlog Entry - 30 May 2026</title>
<meta
property="og:title"
content="Devlog Entry - 30 May 2026">
<meta
property="og:url"
content="https://aklabs.net/2026/05/30/Devlog-20260530/index.html">
<meta
property="og:img"
content="/images/akesterson.webp">
<meta
property="og:type"
content="article">
<meta
property="og:article:published_time"
content="2026-05-30">
<meta
property="og:article:modified_time"
content="2026-05-30">
<meta
property="og:article:author"
content="Andrew Kesterson">
<link rel="preload" href="//at.alicdn.com/t/font_1946621_i1kgafibvw.css" as="style" >
<link rel="preload" href="//at.alicdn.com/t/font_1952792_89b4ac4k4up.css" as="style" >
<link rel="preload" href="/css/main.css" as="style" >
<link rel="modulepreload" href="//instant.page/5.1.0">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_1946621_i1kgafibvw.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_1952792_89b4ac4k4up.css">
<link rel="stylesheet" href="/js/lib/lightbox/baguetteBox.min.css">
<script>
function loadScript(url, cb) {
var script = document.createElement('script');
script.src = url;
if (cb) script.onload = cb;
script.async = true;
document.body.appendChild(script);
}
function loadCSS(href, data, attr) {
var sheet = document.createElement('link');
sheet.ref = 'stylesheet';
sheet.href = href;
sheet.dataset[data] = attr;
document.head.appendChild(sheet);
}
function changeCSS(cssFile, data, attr) {
var oldlink = document.querySelector(data);
var newlink = document.createElement("link");
newlink.setAttribute("rel", "stylesheet");
newlink.setAttribute("href", cssFile);
newlink.dataset.prism = attr;
document.head.replaceChild(newlink, oldlink);
}
</script>
<script>
function prismThemeChange() {
if(document.getElementById('theme-color').dataset.mode === 'dark') {
if(document.querySelector('[data-prism]')) {
changeCSS('/js/lib/prism/prism-tomorrow.min.css', '[data-prism]', 'prism-tomorrow');
} else {
loadCSS('/js/lib/prism/prism-tomorrow.min.css', 'prism', 'prism-tomorrow');
}
} else {
if(document.querySelector('[data-prism]')) {
changeCSS('/js/lib/prism/prism-defauult.min.css', '[data-prism]', 'prism-defauult');
} else {
loadCSS('/js/lib/prism/prism-defauult.min.css', 'prism', 'prism-defauult');
}
}
}
prismThemeChange()
</script>
<link rel="stylesheet" href="/js/lib/prism/prism-line-numbers.min.css">
<script>
// control reverse button
var reverseDarkList = {
dark: 'light',
light: 'dark'
};
var themeColor = {
dark: '#1c1c1e',
light: '#fff'
}
// get the data of css prefers-color-scheme
var getCssMediaQuery = function() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
// reverse current darkmode setting function
var reverseDarkModeSetting = function() {
var setting = localStorage.getItem('user-color-scheme');
if(reverseDarkList[setting]) {
setting = reverseDarkList[setting];
} else if(setting === null) {
setting = reverseDarkList[getCssMediaQuery()];
} else {
return;
}
localStorage.setItem('user-color-scheme', setting);
return setting;
};
// apply current darkmode setting
</script>
<script>
var setDarkmode = function(mode) {
var setting = mode || localStorage.getItem('user-color-scheme');
if(setting === getCssMediaQuery()) {
document.documentElement.removeAttribute('data-user-color-scheme');
localStorage.removeItem('user-color-scheme');
document.getElementById('theme-color').content = themeColor[setting];
document.getElementById('theme-color').dataset.mode = setting;
prismThemeChange();
} else if(reverseDarkList[setting]) {
document.documentElement.setAttribute('data-user-color-scheme', setting);
document.getElementById('theme-color').content = themeColor[setting];
document.getElementById('theme-color').dataset.mode = setting;
prismThemeChange();
} else {
document.documentElement.removeAttribute('data-user-color-scheme');
localStorage.removeItem('user-color-scheme');
document.getElementById('theme-color').content = themeColor[getCssMediaQuery()];
document.getElementById('theme-color').dataset.mode = getCssMediaQuery();
prismThemeChange();
}
};
setDarkmode();
</script>
<link rel="preload" href="/js/lib/lightbox/baguetteBox.min.js" as="script">
<link rel="preload" href="/js/lib/lightbox/baguetteBox.min.css" as="style" >
<link rel="preload" href="/js/lib/lozad.min.js" as="script">
<meta name="generator" content="Hexo 6.0.0"><link rel="alternate" href="/atom.xml" title="AKLabs" type="application/atom+xml">
</head>
<body>
<div class="wrapper">
<nav class="navbar">
<div class="navbar-logo">
<a class="navbar-logo-main" href="/">
<span class="navbar-logo-dsc">AKLabs</span>
</a>
</div>
<div class="navbar-menu">
<a
href="/now"
class="navbar-menu-item">
~/.plan
</a>
<a
href="/archives"
class="navbar-menu-item">
Archive
</a>
<a
href="/categories"
class="navbar-menu-item">
Categories
</a>
<a
href="/about"
class="navbar-menu-item">
About
</a>
<a
href="/contact"
class="navbar-menu-item">
Contact
</a>
<button
class="navbar-menu-item darknavbar navbar-menu-btn"
aria-label="Toggle dark mode"
id="dark">
<i class="iconfont icon-weather"></i>
</button>
<button
class="navbar-menu-item searchnavbar navbar-menu-btn"
aria-label="Toggle search"
id="search">
<!-- <i
class="iconfont icon-search"
style="font-size: 1.2rem; font-weight: 400;">
</i> -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
class="iconify iconify--ion" width="28" height="28" preserveAspectRatio="xMidYMid meet" viewBox="0 0 512 512">
<path fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="28"
d="M256 80a176 176 0 1 0 176 176A176 176 0 0 0 256 80Z"></path>
<path fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="28"
d="M232 160a72 72 0 1 0 72 72a72 72 0 0 0-72-72Z"></path>
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="28"
d="M283.64 283.64L336 336"></path>
</svg>
</button>
</div>
</nav>
<div
id="local-search"
style="display: none">
<input
class="navbar-menu-item"
id="search-input"
placeholder="请输入搜索内容..." />
<div id="search-content"></div>
</div>
<div class="section-wrap">
<div class="container">
<div class="columns">
<aside class="left-column">
<div class="card card-author">
<img
src="/images/akesterson.webp"
class="author-img"
width="88"
height="88"
alt="author avatar">
<p class="author-name">Andrew Kesterson</p>
<p class="author-description"><center><i>"Love God. Live Righteously. Die Well."</i> <br/> <br/> <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew">Source Code</a> || <a target="_blank" rel="noopener" href="https://www.linkedin.com/in/andrewkesterson/">LinkedIn</a> <br/> </center></p>
<div class="author-message">
<a
class="author-posts-count"
href="/archives">
<span>32</span>
<span>Posts</span>
</a>
<a
class="author-categories-count"
href="/categories">
<span>9</span>
<span>Categories</span>
</a>
<a
class="author-tags-count"
href="/tags">
<span>0</span>
<span>Tags</span>
</a>
</div>
</div>
<div class="sticky-tablet">
<article class="display-when-two-columns spacer">
<div class="card card-content toc-card">
<div class="toc-header">
<i
class="iconfont icon-menu"
style="padding-right: 2px;">
</i>TOC
</div>
<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#libakstdlib"><span class="toc-text">libakstdlib</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#libakgl"><span class="toc-text">libakgl</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Locking-game-state-for-thread-safety"><span class="toc-text">Locking game state for thread safety</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Controllers"><span class="toc-text">Controllers</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Pluggable-rendering-backends"><span class="toc-text">Pluggable rendering backends</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Pluggable-physics-systems"><span class="toc-text">Pluggable physics systems</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Tests-and-Documentation"><span class="toc-text">Tests and Documentation</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ESP32-projects"><span class="toc-text">ESP32 projects</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Flowing-LED-lights-with-a-button"><span class="toc-text">Flowing LED lights with a button</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Controlling-buzzers-with-transistors"><span class="toc-text">Controlling buzzers with transistors</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Communicating-over-Serial-UART"><span class="toc-text">Communicating over Serial UART</span></a></li></ol></li></ol>
</div>
</article>
<article class="card card-content categories-widget">
<div class="categories-card">
<div class="categories-header">
<i
class="iconfont icon-fenlei"
style="padding-right: 2px;">
</i>Categories
</div>
<div class="categories-list">
<a href="/categories/Books/">
<div class="categories-list-item">
Books
<span class="categories-list-item-badge">14</span>
</div>
</a>
<a href="/categories/Faith/">
<div class="categories-list-item">
Faith
<span class="categories-list-item-badge">7</span>
</div>
</a>
<a href="/categories/Outdoors/">
<div class="categories-list-item">
Outdoors
<span class="categories-list-item-badge">1</span>
</div>
</a>
<a href="/categories/Philosophy/">
<div class="categories-list-item">
Philosophy
<span class="categories-list-item-badge">6</span>
</div>
</a>
<a href="/categories/Leadership/">
<div class="categories-list-item">
Leadership
<span class="categories-list-item-badge">8</span>
</div>
</a>
<a href="/categories/History/">
<div class="categories-list-item">
History
<span class="categories-list-item-badge">1</span>
</div>
</a>
<a href="/categories/Liberal-Education/">
<div class="categories-list-item">
Liberal-Education
<span class="categories-list-item-badge">1</span>
</div>
</a>
<a href="/categories/Technology/">
<div class="categories-list-item">
Technology
<span class="categories-list-item-badge">13</span>
</div>
</a>
<a href="/categories/Current-Events/">
<div class="categories-list-item">
Current-Events
<span class="categories-list-item-badge">5</span>
</div>
</a>
</div>
</div>
</article>
<article class="card card-content tags-widget">
<div class="tags-card">
<div class="tags-header">
<i
class="iconfont icon-biaoqian"
style="padding-right: 2px;">
</i>hot tags
</div>
<div class="tags-list">
</div>
</div>
</article>
</div>
</aside>
<main class="main-column">
<article class="card card-content">
<header>
<h1 class="post-title">
Devlog Entry - 30 May 2026
</h1>
</header>
<div class="post-meta post-show-meta">
<time datetime="2026-05-30T14:05:47.000Z">
<i
class="iconfont icon-calendar"
style="margin-right: 2px;">
</i>
<span>2026-05-30</span>
</time>
<span class="dot"></span>
<a
href="/categories/Technology/"
class="post-meta-link">
Technology
</a>
<span class="dot"></span>
<span>1.7k words</span>
</div>
</header>
<div
id="section"
class="post-content">
<h2 id="libakstdlib"><a href="#libakstdlib" class="headerlink" title="libakstdlib"></a>libakstdlib</h2><p>Several improvements were driven into <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/libakstdlib/">libakstdlib</a>, my libc layer that wraps my platforms native libc in <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/libakerror/">libakerror</a>.</p>
<ul>
<li>Added <code>aksl_atol</code></li>
<li>Added <code>aksl_atoll</code></li>
<li>Added <code>aksl_atoi</code></li>
<li>Fixed a bug in <code>aksl_sprintf</code>, it wasnt using va_args properly, leading to segfault</li>
<li>Added <code>aksl_atof</code></li>
<li>Added <code>aksl_realpath</code>, which takes a string that contains a filesystem path which may or may not be relative, and populates a destination string with the absolute path to that file reference</li>
</ul>
<pre class="line-numbers language-c" data-language="c"><code class="language-c">akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token function">aksl_atoi</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>nptr<span class="token punctuation">,</span> <span class="token keyword">int</span> <span class="token operator">*</span>dest<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token function">aksl_atol</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>nptr<span class="token punctuation">,</span> <span class="token keyword">long</span> <span class="token operator">*</span>dest<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token function">aksl_atoll</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>nptr<span class="token punctuation">,</span> <span class="token keyword">long</span> <span class="token keyword">long</span> <span class="token operator">*</span>dest<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token function">aksl_atof</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>nptr<span class="token punctuation">,</span> <span class="token keyword">double</span> <span class="token operator">*</span>dest<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token function">aksl_realpath</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>restrict path<span class="token punctuation">,</span> <span class="token keyword">char</span> <span class="token operator">*</span>restrict resolved_path<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<h2 id="libakgl"><a href="#libakgl" class="headerlink" title="libakgl"></a>libakgl</h2><p>I made several improvements to my C&#x2F;SDL3 game library.</p>
<h3 id="Locking-game-state-for-thread-safety"><a href="#Locking-game-state-for-thread-safety" class="headerlink" title="Locking game state for thread safety"></a>Locking game state for thread safety</h3><p>The SDL3 implementation is, by default, implemented with threaded events. For the <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/akgl-test/">akgl-test demonstration app</a> Im writing alongside <code>libakgl</code>, this eventually reared its ugly head as some nasty race conditions on the various <code>AKGL_HEAP</code> objects that are statically allocated and centrally managed. Consider this:</p>
<ul>
<li>Input processing happens during <code>SDL_AppEvent</code></li>
<li>Physics and rendering happens during <code>SDL_AppIterate</code></li>
<li>The player presses <code>ESC</code> to go back to the main menu to select a different demonstration</li>
<li>Part of that process is that the <code>AKGL_HEAP_ACTORS</code> is reset</li>
<li>While that is happening, the <code>SDL_AppIterate</code> fires from another thread. It goes to render actors that are in the process of being reset.</li>
<li>KABOOM</li>
</ul>
<p>To resolve this quickly, I added a <code>SDL_Mutex</code> object to the global <code>akgl_Game</code> object. Anything that modifies the core game state (like the <code>AKGL_HEAP</code> objects) calls <code>akgl_game_state_lock()</code> to lock the state, and <code>akgl_game_state_unlock()</code> when they are done. This resolved the race condition.</p>
<p>Right now this is being applied on some really large operations. It can probably be applied in a far more granular fashion. But for right now it works.</p>
<h3 id="Controllers"><a href="#Controllers" class="headerlink" title="Controllers"></a>Controllers</h3><p>Added a method (<code>akgl_controller_open_gamepads()</code>) that automatically opens all connected gamepads at the time of <code>akgl_game_init()</code>. </p>
<ul>
<li>The user doesnt have to call this, its done automatically</li>
<li>You have to open a gamepad before you can receive events from it. So asking the player “Press any button on the controller” to figure out which controller theyre using doesnt work until those gamepads are open. There may be consequences to opening gamepads we dont need - I dont know yet. If that comes up, its easy enough to close the ones we arent using.</li>
</ul>
<h3 id="Pluggable-rendering-backends"><a href="#Pluggable-rendering-backends" class="headerlink" title="Pluggable rendering backends"></a>Pluggable rendering backends</h3><p>At some point I am going to want to use the SDL3 GPU renderer as the primary renderer for my engine. Right now Im using the Rendering API because its familiar, comfortable, and very simple for the 2D stuff Im doing right now. But I need to reduce the coupling between the rendering API and the rest of the game logic. So I abstracted away the existing SDL_Renderer rendering backend into a new <code>akgl_RenderBackend</code> structure, and refactored everything to call the methods on that structure instead of touching the renderer directly.</p>
<pre class="line-numbers language-c" data-language="c"><code class="language-c"><span class="token keyword">typedef</span> <span class="token keyword">struct</span> <span class="token class-name">akgl_RenderBackend</span> <span class="token punctuation">&#123;</span>
SDL_Renderer <span class="token operator">*</span>sdl_renderer<span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token punctuation">(</span><span class="token operator">*</span>shutdown<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">akgl_RenderBackend</span> <span class="token operator">*</span>self<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token punctuation">(</span><span class="token operator">*</span>frame_start<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">akgl_RenderBackend</span> <span class="token operator">*</span>self<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token punctuation">(</span><span class="token operator">*</span>frame_end<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">akgl_RenderBackend</span> <span class="token operator">*</span>self<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token punctuation">(</span><span class="token operator">*</span>draw_texture<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">akgl_RenderBackend</span> <span class="token operator">*</span>self<span class="token punctuation">,</span> SDL_Texture <span class="token operator">*</span>texture<span class="token punctuation">,</span> SDL_FRect <span class="token operator">*</span>src<span class="token punctuation">,</span> SDL_FRect <span class="token operator">*</span>dest<span class="token punctuation">,</span> <span class="token keyword">double</span> angle<span class="token punctuation">,</span> SDL_FPoint <span class="token operator">*</span>center<span class="token punctuation">,</span> SDL_FlipMode flip<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token punctuation">(</span><span class="token operator">*</span>draw_mesh<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">akgl_RenderBackend</span> <span class="token operator">*</span>self<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token punctuation">(</span><span class="token operator">*</span>draw_world<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">akgl_RenderBackend</span> <span class="token operator">*</span>self<span class="token punctuation">,</span> akgl_Iterator <span class="token operator">*</span>opflags<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span> akgl_RenderBackend<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>The usage of this new abstraction is pretty simple:</p>
<pre class="line-numbers language-c" data-language="c"><code class="language-c"><span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_game_init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_game_state_lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_registry_load_properties</span><span class="token punctuation">(</span><span class="token string">"assets/properties.json"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_render_init2d</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>renderer<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_game_state_unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>The <code>akgl_render_init2d</code> function sets up the renderer with a bunch of backend functions that know how to render the various on-screen parts of libakgl (like <code>akgl_Actor</code>, <code>akgl_Sprite</code>, <code>akgl_Tilemap</code>, etc) through the SDL Rendering API. All of this detail is transparent to theuser, and to the library author as well, because they just do things <code>renderer-&gt;draw_texture(&amp;renderer)</code> and <code>renderer-&gt;draw_world(&amp;renderer)</code>. The real detail of how that all gets done is tucked away <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/libakgl/src/branch/main/src/renderer.c">in those functions</a>.</p>
<p>Now <code>SDL_AppIterate</code> can look like this to draw the world and all the actors in it:</p>
<pre class="line-numbers language-none"><code class="language-none">PASS(e, renderer.frame_start(&amp;renderer));
PASS(e, akgl_game_update(NULL));
PASS(e, renderer.frame_end(&amp;renderer));<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
<p>… because <code>akgl_game_update</code> calls <code>renderer-&gt;draw_world()</code> for you.</p>
<p>Now when I want to implement a GPU renderer (or any other kind of renderer, really - ASCII ncurses anyone?), all I have to do is write some new pluggable methods, write an initializer function like <code>akgl_render_init2d()</code> that populates a renderer structure, and were off to the races.</p>
<p>At least thats the idea. Well see what happens when I implement SDL3 GPU at some point in the future.</p>
<h2 id="Pluggable-physics-systems"><a href="#Pluggable-physics-systems" class="headerlink" title="Pluggable physics systems"></a>Pluggable physics systems</h2><p>Following on the pluggable rendering system, I am at the point where I need to put in some amount of physics simulation before I can start making games with this thing. So I followed the same basic pattern I did with the rendering backend, and made a structure that holds a bunch of function pointers and other information</p>
<pre class="line-numbers language-c" data-language="c"><code class="language-c"><span class="token keyword">typedef</span> <span class="token keyword">struct</span> <span class="token class-name">akgl_PhysicsBackend</span> <span class="token punctuation">&#123;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token punctuation">(</span><span class="token operator">*</span>simulate<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">akgl_PhysicsBackend</span> <span class="token operator">*</span>self<span class="token punctuation">,</span> akgl_Iterator <span class="token operator">*</span>opflags<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token punctuation">(</span><span class="token operator">*</span>gravity<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">akgl_PhysicsBackend</span> <span class="token operator">*</span>self<span class="token punctuation">,</span> akgl_Actor <span class="token operator">*</span>actor<span class="token punctuation">,</span> <span class="token class-name">float32_t</span> dt<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token punctuation">(</span><span class="token operator">*</span>collide<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">akgl_PhysicsBackend</span> <span class="token operator">*</span>self<span class="token punctuation">,</span> akgl_Actor <span class="token operator">*</span>a1<span class="token punctuation">,</span> akgl_Actor <span class="token operator">*</span>a2<span class="token punctuation">)</span><span class="token punctuation">;</span>
akerr_ErrorContext AKERR_NOIGNORE <span class="token operator">*</span><span class="token punctuation">(</span><span class="token operator">*</span>move<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">akgl_PhysicsBackend</span> <span class="token operator">*</span>self<span class="token punctuation">,</span> akgl_Actor <span class="token operator">*</span>actor<span class="token punctuation">,</span> <span class="token class-name">float32_t</span> dt<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">double</span> drag_x<span class="token punctuation">;</span>
<span class="token keyword">double</span> drag_y<span class="token punctuation">;</span>
<span class="token keyword">double</span> drag_z<span class="token punctuation">;</span>
<span class="token keyword">double</span> gravity_x<span class="token punctuation">;</span>
<span class="token keyword">double</span> gravity_y<span class="token punctuation">;</span>
<span class="token keyword">double</span> gravity_z<span class="token punctuation">;</span>
SDL_Time gravity_time<span class="token punctuation">;</span>
SDL_Time timer_gravity<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span> akgl_PhysicsBackend<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>… and made it easy to initialize with the backend of my choice:</p>
<pre class="line-numbers language-c" data-language="c"><code class="language-c"><span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_game_init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_game_state_lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_registry_load_properties</span><span class="token punctuation">(</span><span class="token string">"assets/properties.json"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_physics_init_sidescroller</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>physics<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_game_state_unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>… and now simulating physics in the world full of actors is as simple as calling one method inside of <code>SDL_AppIterate</code>:</p>
<pre class="line-numbers language-c" data-language="c"><code class="language-c"><span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> renderer<span class="token punctuation">.</span><span class="token function">frame_start</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>renderer<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token function">akgl_game_update</span><span class="token punctuation">(</span><span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> renderer<span class="token punctuation">.</span><span class="token function">frame_end</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>renderer<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
<p>… because <code>akgl_game_update()</code> calls the physics simulator for you:</p>
<pre class="line-numbers language-c" data-language="c"><code class="language-c"><span class="token function">PASS</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> physics<span class="token punctuation">.</span><span class="token function">simulate</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>physics<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
<p>… and <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/libakgl/src/branch/main/src/physics.c">the physics backend</a> takes care of calculating acceleration, velocity, movement, and (soon) collisions.</p>
<p>Collision detection isnt there yet. Its coming soon, with both other actors and world geometry.</p>
<h3 id="Tests-and-Documentation"><a href="#Tests-and-Documentation" class="headerlink" title="Tests and Documentation"></a>Tests and Documentation</h3><p>This thing is basically completely undocumented. Thats okay, because Im only really writing it for myself, but as the complexit grows, I really should write some docs. Ill put <em>something</em> together. </p>
<p>I really need to write some new tests. I was very disciplined about that in the early days of this library. But then again, back then, I didnt have demo games to work with. So the testing harness was the only metric for success I had. But Ive gotten away from writing or even checking the test harness, and thats bad. Future updates will see me do <em>something</em> about this.</p>
<h2 id="ESP32-projects"><a href="#ESP32-projects" class="headerlink" title="ESP32 projects"></a>ESP32 projects</h2><p>Ive started studying embedded development. Ive got a long road ahead of me, but it all starts with an ESP32-S3 Arduino kit. Even with the experience I have with technology, I figured it was best to start somewhere simple before diving in to RTOS and FPGA on more complex platforms. I completed 3 projects on my Freenove ESP32-S3 WROOM devkit this week.</p>
<h3 id="Flowing-LED-lights-with-a-button"><a href="#Flowing-LED-lights-with-a-button" class="headerlink" title="Flowing LED lights with a button"></a>Flowing LED lights with a button</h3><blockquote class="twitter-tweet"><p lang="en" dir="ltr">The journey of a thousand miles begins with an esp32, some blinking leds and a button <a target="_blank" rel="noopener" href="https://t.co/i9rOphuY60">pic.twitter.com/i9rOphuY60</a></p>&mdash; Andrew Kesterson (@AKLabsDotNet) <a target="_blank" rel="noopener" href="https://x.com/AKLabsDotNet/status/2058655798591476058?ref_src=twsrc%5Etfw">May 24, 2026</a></blockquote> <script async src="https://platform.x.com/widgets.js" charset="utf-8"></script>
<p><a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/esp32-learning/src/branch/main/01-flowing_light_with_button/flowing_light_with_button.ino">This program</a> sends a cascading light effect through the LED bar that fades on either side of a strong central point. It normally flows in one direction, but if you press the button, it changes direction.</p>
<p>I learned several things from this project:</p>
<ul>
<li>How to use the Arduino IDE to deploy code to the ESP32-S3 WROOM</li>
<li>How to use the Arduino IDE debugger to step through code running on the device</li>
<li>How to configure GPIO pins on the ESP32-S3 as input or output</li>
<li>How to read and write data to and from those GPIO pins</li>
<li>Using a physical button to control program flow in the microcontroller</li>
<li>Debouncing mechanical buttons</li>
<li>What is pulse width modulation and how do we control it on the ESP32-S3</li>
<li>Using pulse width modulation with an LED to create a smooth visual effect simulating an analog curve</li>
</ul>
<p>There is a more detailed writeup about this project <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/esp32-learning/src/branch/main/01-flowing_light_with_button">in my source repository</a></p>
<h3 id="Controlling-buzzers-with-transistors"><a href="#Controlling-buzzers-with-transistors" class="headerlink" title="Controlling buzzers with transistors"></a>Controlling buzzers with transistors</h3><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Love it when the manufacturers tutorials have bugs in them. Still better than diagnosing istio problems in kubernetes. One project down, thousands more to go. <a target="_blank" rel="noopener" href="https://t.co/53WQetCTmQ">pic.twitter.com/53WQetCTmQ</a></p>&mdash; Andrew Kesterson (@AKLabsDotNet) <a target="_blank" rel="noopener" href="https://x.com/AKLabsDotNet/status/2059735218622538228?ref_src=twsrc%5Etfw">May 27, 2026</a></blockquote> <script async src="https://platform.x.com/widgets.js" charset="utf-8"></script>
<p>These <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/esp32-learning/src/branch/main/02-buzzers_with_transistors/buzzer-npn-lowside-switching/buzzer-npn-lowside-switching.ino">two</a> <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/esp32-learning/src/branch/main/02-buzzers_with_transistors/activebuzzer_npn_lowside_switching/activebuzzer_npn_lowside_switching.ino">programs</a> allow you to control a buzzer with a button to make a noise on command. They both use the exact same breadboard, the only difference is that one uses a passive buzzer, while the other uses an active buzzer (pn TMB12A05).</p>
<p>I learned several things from these projects:</p>
<ul>
<li>The differences in active vs passive buzzers</li>
<li>How transistors work, and how you can use them to control the flow of power in a circuit</li>
<li>Why you might place a transistor upstream or downstream of the component whose power you are controlling</li>
<li>How to turn a transistor on or off using the GPIO pin on an ESP32</li>
<li>How to use my oscilloscope to diagnose PWM output issues</li>
</ul>
<p>There is a more detailed writeup about this project <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/esp32-learning/src/branch/main/02-buzzers_with_transistors">in my source repository</a></p>
<h3 id="Communicating-over-Serial-UART"><a href="#Communicating-over-Serial-UART" class="headerlink" title="Communicating over Serial UART"></a>Communicating over Serial UART</h3><blockquote class="twitter-tweet"><p lang="en" dir="ltr">A good digital logic analyzer is worth its weight in gold<br><br>Thankfully they tend to be lightweight devices <a target="_blank" rel="noopener" href="https://t.co/FlSPL95DyW">pic.twitter.com/FlSPL95DyW</a></p>&mdash; Andrew Kesterson (@AKLabsDotNet) <a target="_blank" rel="noopener" href="https://x.com/AKLabsDotNet/status/2060156789111316602?ref_src=twsrc%5Etfw">May 29, 2026</a></blockquote> <script async src="https://platform.x.com/widgets.js" charset="utf-8"></script>
<p>This project shows using the ESP32-S3 serial port for communication via UART.</p>
<p>Lessons Learned:</p>
<ul>
<li>The Arduino HAL is surprisingly thick</li>
<li>You cant use the ESP32-S3 UART for communication via USB when youre debugging</li>
<li>libc is libc anywhere you go</li>
<li>How to use my digital logic analyzer to snoop on, record and inspect UART protocol traffic</li>
<li>How to use my oscilloscope to look at UART traffic over the wire, and how that compares to using the logic analyzer</li>
</ul>
<p>There is a more detailed writeup about this project <a target="_blank" rel="noopener" href="https://source.starfort.tech/andrew/esp32-learning/src/branch/main/03-serialuart">in my source repository</a></p>
<!--
-- tags
INFO Validating config
INFO Start processing
Name Posts Path
No tags.
---------------
-- categories
INFO Validating config
INFO Start processing
Name Posts
Books 14
Current Events 5
Current Events 0
Faith 6
History 1
Leadership 8
Liberal Education 1
Outdoors 1
Philosophy 5
Philosophy 0
Technology 11
technology 0
--!>
</div>
<div>
</div>
</article>
<div class="nav">
<div class="nav-item-prev">
<a
href="/2026/06/08/Devlog-20260608/"
class="nav-link">
<i class="iconfont icon-left nav-prev-icon"></i>
<div>
<div class="nav-label">Prev</div>
<div class="nav-title">Devlog Entry - 8 June 2026 </div>
</div>
</a>
</div>
<div class="nav-item-next">
<a
href="/2026/05/15/Devlog-20260518/"
class="nav-link">
<div>
<div class="nav-label">Next</div>
<div class="nav-title">Devlog Entry - 18 May 2026 </div>
</div>
<i class="iconfont icon-right nav-next-icon"></i>
</a>
</div>
</div>
<div
class="card card-content toc-card"
id="mobiletoc">
<div class="toc-header">
<i
class="iconfont icon-menu"
style="padding-right: 2px;">
</i>TOC
</div>
<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#libakstdlib"><span class="toc-text">libakstdlib</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#libakgl"><span class="toc-text">libakgl</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Locking-game-state-for-thread-safety"><span class="toc-text">Locking game state for thread safety</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Controllers"><span class="toc-text">Controllers</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Pluggable-rendering-backends"><span class="toc-text">Pluggable rendering backends</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Pluggable-physics-systems"><span class="toc-text">Pluggable physics systems</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Tests-and-Documentation"><span class="toc-text">Tests and Documentation</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ESP32-projects"><span class="toc-text">ESP32 projects</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Flowing-LED-lights-with-a-button"><span class="toc-text">Flowing LED lights with a button</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Controlling-buzzers-with-transistors"><span class="toc-text">Controlling buzzers with transistors</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Communicating-over-Serial-UART"><span class="toc-text">Communicating over Serial UART</span></a></li></ol></li></ol>
</div>
</main>
<aside class="right-column">
<div class="sticky-widescreen">
<article class="card card-content toc-card">
<div class="toc-header">
<i
class="iconfont icon-menu"
style="padding-right: 2px;">
</i>TOC
</div>
<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#libakstdlib"><span class="toc-text">libakstdlib</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#libakgl"><span class="toc-text">libakgl</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Locking-game-state-for-thread-safety"><span class="toc-text">Locking game state for thread safety</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Controllers"><span class="toc-text">Controllers</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Pluggable-rendering-backends"><span class="toc-text">Pluggable rendering backends</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Pluggable-physics-systems"><span class="toc-text">Pluggable physics systems</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Tests-and-Documentation"><span class="toc-text">Tests and Documentation</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#ESP32-projects"><span class="toc-text">ESP32 projects</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Flowing-LED-lights-with-a-button"><span class="toc-text">Flowing LED lights with a button</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Controlling-buzzers-with-transistors"><span class="toc-text">Controlling buzzers with transistors</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Communicating-over-Serial-UART"><span class="toc-text">Communicating over Serial UART</span></a></li></ol></li></ol>
</article>
<article class="card card-content">
<div class="recent-posts-card">
<div class="recent-posts-header">
<i
class="iconfont icon-wenzhang_huaban"
style="padding-right: 2px;">
</i>Recent Posts
</div>
<div class="recent-posts-list">
<div class="recent-posts-item">
<div class="recent-posts-item-title">2026-06-08</div>
<a href="/2026/06/08/Carrying-That-Weight/"><div class="recent-posts-item-content">Carrying That Weight</div></a>
</div>
<div class="recent-posts-item">
<div class="recent-posts-item-title">2026-06-08</div>
<a href="/2026/06/08/Devlog-20260608/"><div class="recent-posts-item-content">Devlog Entry - 8 June 2026</div></a>
</div>
<div class="recent-posts-item">
<div class="recent-posts-item-title">2026-05-30</div>
<a href="/2026/05/30/Devlog-20260530/"><div class="recent-posts-item-content">Devlog Entry - 30 May 2026</div></a>
</div>
<div class="recent-posts-item">
<div class="recent-posts-item-title">2026-05-15</div>
<a href="/2026/05/15/Devlog-20260518/"><div class="recent-posts-item-content">Devlog Entry - 18 May 2026</div></a>
</div>
</div>
</div>
</article>
</div>
</aside>
</div>
</div>
</div>
</div>
<footer class="footer">
<div class="footer-container">
<div>
<div class="footer-dsc">
<span>
Copyright ©
-
2026
</span>
&nbsp;
<a
href="mailto:andrew@aklabs.net"
class="footer-link">
Andrew Kesterson
</a>
<br/>
</div>
</div>
<div class="footer-dsc">
Powered by
<a
href="https://hexo.io/"
class="footer-link"
target="_blank"
rel="nofollow noopener noreferrer">
&nbsp;Hexo
</a>
<span>&nbsp;|&nbsp;</span>
Theme -
<a
href="https://github.com/theme-kaze"
class="footer-link"
target="_blank"
rel="nofollow noopener noreferrer">
&nbsp;Kaze
</a>
</div>
</footer>
<a
role="button"
id="scrollbutton"
class="basebutton"
aria-label="回到顶部">
<i class="iconfont icon-arrowleft button-icon"></i>
</a>
<a
role="button"
id="menubutton"
aria-label="menu button"
class="basebutton">
<i class="iconfont icon-menu button-icon"></i>
</a>
<a
role="button"
id="popbutton"
class="basebutton"
aria-label="控制中心">
<i class="iconfont icon-expand button-icon"></i>
</a>
<a
role="button"
id="darkbutton"
class="basebutton darkwidget"
aria-label="夜色模式">
<i class="iconfont icon-weather button-icon"></i>
</a>
<a
role="button"
id="searchbutton"
class="basebutton searchwidget"
aria-label="搜索">
<i class="iconfont icon-search button-icon"></i>
</a>
<script>
var addImgLayout = function () {
var img = document.querySelectorAll('.post-content img')
var i
for (i = 0; i < img.length; i++) {
var wrapper = document.createElement('a')
wrapper.setAttribute('href', img[i].getAttribute('data-src'))
wrapper.setAttribute('aria-label', 'illustration')
wrapper.style.cssText =
'width: 100%; display: flex; justify-content: center;'
if (img[i].alt) wrapper.dataset.caption = img[i].alt
wrapper.dataset.nolink = true
img[i].before(wrapper)
wrapper.append(img[i])
var divWrap = document.createElement('div')
divWrap.classList.add('gallery')
wrapper.before(divWrap)
divWrap.append(wrapper)
}
baguetteBox.run('.gallery')
}
</script>
<script>
loadScript(
"/js/lib/lightbox/baguetteBox.min.js",
addImgLayout
)
</script>
<script src="/js/main.js"></script>
<script>
var addLazyload = function () {
var observer = lozad('.lozad', {
load: function (el) {
el.srcset = el.getAttribute('data-src')
},
loaded: function (el) {
el.classList.add('loaded')
},
})
observer.observe()
}
</script>
<script>
loadScript('/js/lib/lozad.min.js', addLazyload)
</script>
<script src="//instant.page/5.1.0" type="module"
integrity="sha384-by67kQnR+pyfy8yWP4kPO12fHKRLHZPfEsiSXR8u2IKcTdxD805MGUXBzVPnkLHw"></script>
<script>
var googleAnalytics = function () {
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
gtag('js', new Date())
gtag('config', 'G-S3YLF516N6')
}
</script>
<script>
loadScript(
'https://www.googletagmanager.com/gtag/js?id=' +
'G-S3YLF516N6',
googleAnalytics
)
</script>
</body>
</html>