Remove typing, work on stopwatches and debug

This commit is contained in:
Sky Johnson 2024-10-05 18:28:04 -05:00
parent 36d8f3405f
commit 3c0e7590ce
16 changed files with 123 additions and 38 deletions

Binary file not shown.

View File

@ -38,7 +38,10 @@ router_post($r, '/character/delete', 'char_controller_delete_post');
Router Router
*/ */
// [code, handler, params] // [code, handler, params]
stopwatch_start('router');
$l = router_lookup($r, $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']); $l = router_lookup($r, $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
stopwatch_stop('router');
if ($l['code'] !== 200) router_error($l['code']); if ($l['code'] !== 200) router_error($l['code']);
$l['handler'](...$l['params'] ?? []); $l['handler'](...$l['params'] ?? []);
@ -46,3 +49,8 @@ $l['handler'](...$l['params'] ?? []);
Cleanup Cleanup
*/ */
clear_flashes(); clear_flashes();
/*
Stopwatch
*/
if (env('debug')) echo c_debug_stopwatch();

View File

@ -76,3 +76,12 @@ function must_have_character()
change_user_character($char['id']); change_user_character($char['id']);
} }
} }
/**
* The user must be authenticated and have a character.
*/
function auth_only_and_must_have_character()
{
auth_only();
must_have_character();
}

View File

@ -23,6 +23,9 @@ require_once SRC . '/models/char.php';
require_once SRC . '/controllers/char.php'; require_once SRC . '/controllers/char.php';
require_once SRC . '/controllers/auth.php'; require_once SRC . '/controllers/auth.php';
// Track the start time of the request
define('START_TIME', microtime(true));
/* /*
Load env, set error reporting, etc. Load env, set error reporting, etc.
*/ */
@ -34,14 +37,19 @@ if (env('debug') === 'true') {
error_reporting(E_ALL); error_reporting(E_ALL);
} }
stopwatch_start('bootstrap'); // Start the bootstrap stopwatch
// Generate a new CSRF token. (if one doesn't exist, that is) // Generate a new CSRF token. (if one doesn't exist, that is)
csrf(); csrf();
// Have a global counter for queries // Have global counters for queries
$GLOBALS['queries'] = 0; $GLOBALS['queries'] = 0;
$GLOBALS['query_time'] = 0;
// Set the default page layout // Set the default page layout
page_layout('basic'); page_layout('basic');
// Run auth_check to see if we're logged in, since it populates the user data in SESSION // Run auth_check to see if we're logged in, since it populates the user data in SESSION
auth_check(); auth_check();
stopwatch_stop('bootstrap'); // Stop the bootstrap stopwatch

View File

@ -49,7 +49,7 @@ function c_debug_query_log()
/** /**
* Render the character select radio buttons. * Render the character select radio buttons.
*/ */
function c_char_select_box($id, array $char) function c_char_select_box($id, $char)
{ {
return render('components/char_select_box', ['id' => $id, 'char' => $char]); return render('components/char_select_box', ['id' => $id, 'char' => $char]);
} }
@ -88,3 +88,11 @@ function c_form_field($type, $name, $placeholder, $required = false, $autocomple
$html .= $errors !== false && !empty($errors[$name]) ? ' class="form control error"' : ' class="form control"'; $html .= $errors !== false && !empty($errors[$name]) ? ' class="form control error"' : ' class="form control"';
return $html . " autocomplete=\"$autocomplete\">"; return $html . " autocomplete=\"$autocomplete\">";
} }
/**
* Render the stopwatch debug component.
*/
function c_debug_stopwatch()
{
return render('components/debug_stopwatch');
}

View File

@ -5,7 +5,7 @@
*/ */
function char_controller_list_get() function char_controller_list_get()
{ {
auth_only(); must_have_character(); auth_only_and_must_have_character();
$GLOBALS['active_nav_tab'] = 'chars'; $GLOBALS['active_nav_tab'] = 'chars';
echo page('chars/list', ['chars' => char_list(user('id'))]); echo page('chars/list', ['chars' => char_list(user('id'))]);
@ -16,7 +16,7 @@ function char_controller_list_get()
*/ */
function char_controller_list_post() function char_controller_list_post()
{ {
auth_only(); must_have_character(); csrf_ensure(); auth_only_and_must_have_character(); csrf_ensure();
$GLOBALS['active_nav_tab'] = 'chars'; $GLOBALS['active_nav_tab'] = 'chars';
@ -68,7 +68,7 @@ function char_controller_list_post()
*/ */
function char_controller_delete_post() function char_controller_delete_post()
{ {
auth_only(); must_have_character(); csrf_ensure(); auth_only_and_must_have_character(); csrf_ensure();
$char_id = (int) ($_POST['char_id'] ?? 0); $char_id = (int) ($_POST['char_id'] ?? 0);

View File

@ -3,7 +3,7 @@
/** /**
* Open a connection to a database. * Open a connection to a database.
*/ */
function db_open($path): SQLite3 function db_open($path)
{ {
$db = new SQLite3($path); $db = new SQLite3($path);
@ -20,7 +20,7 @@ function db_open($path): SQLite3
/** /**
* Return a connection to the auth database. * Return a connection to the auth database.
*/ */
function db_auth(): SQLite3 function db_auth()
{ {
return $GLOBALS['db_auth'] ??= db_open(__DIR__ . '/../database/auth.db'); return $GLOBALS['db_auth'] ??= db_open(__DIR__ . '/../database/auth.db');
} }
@ -28,7 +28,7 @@ function db_auth(): SQLite3
/** /**
* Return a connection to the live database. * Return a connection to the live database.
*/ */
function db_live(): SQLite3 function db_live()
{ {
return $GLOBALS['db_live'] ??= db_open(__DIR__ . '/../database/live.db'); return $GLOBALS['db_live'] ??= db_open(__DIR__ . '/../database/live.db');
} }
@ -37,7 +37,7 @@ function db_live(): SQLite3
/** /**
* Return a connection to the fights database. * Return a connection to the fights database.
*/ */
function db_fights(): SQLite3 function db_fights()
{ {
return $GLOBALS['db_fights'] ??= db_open(__DIR__ . '/../database/fights.db'); return $GLOBALS['db_fights'] ??= db_open(__DIR__ . '/../database/fights.db');
} }
@ -46,7 +46,7 @@ function db_fights(): SQLite3
/** /**
* Return a connection to the blueprints database. * Return a connection to the blueprints database.
*/ */
function db_blueprints(): SQLite3 function db_blueprints()
{ {
return $GLOBALS['db_blueprints'] ??= db_open(__DIR__ . '/../database/blueprints.db'); return $GLOBALS['db_blueprints'] ??= db_open(__DIR__ . '/../database/blueprints.db');
} }
@ -55,28 +55,32 @@ function db_blueprints(): SQLite3
* Take a SQLite3 database connection, a query string, and an array of parameters. Prepare the query and * Take a SQLite3 database connection, a query string, and an array of parameters. Prepare the query and
* bind the parameters with proper type casting. Then execute the query and return the result. * bind the parameters with proper type casting. Then execute the query and return the result.
*/ */
function db_query(SQLite3 $db, $query, array $params = []) function db_query($db, $query, $params = [])
{ {
$stmt = $db->prepare($query); $stmt = $db->prepare($query);
if (!empty($params)) foreach ($params as $key => $value) $stmt->bindValue($key, $value, getSQLiteType($value)); if (!empty($params)) foreach ($params as $key => $value) $stmt->bindValue($key, $value, getSQLiteType($value));
db_log($query); $start = microtime(true);
return $stmt->execute(); $r = $stmt->execute();
db_log($query, microtime(true) - $start);
return $r;
} }
/** /**
* Take a SQLite3 database connection and a query string. Execute the query and return the result. * Take a SQLite3 database connection and a query string. Execute the query and return the result.
*/ */
function db_exec(SQLite3 $db, $query) function db_exec($db, $query)
{ {
db_log($query); $start = microtime(true);
return $db->exec($query); $r = $db->exec($query);
db_log($query, microtime(true) - $start);
return $r;
} }
/** /**
* Take a SQLite3 database connection, a column name, and a value. Execute a COUNT query to see if the value * Take a SQLite3 database connection, a column name, and a value. Execute a COUNT query to see if the value
* exists in the column. Return true if the value exists, false otherwise. * exists in the column. Return true if the value exists, false otherwise.
*/ */
function db_exists(SQLite3 $db, $table, $column, $value, $caseInsensitive = true) function db_exists($db, $table, $column, $value, $caseInsensitive = true)
{ {
if ($caseInsensitive) { if ($caseInsensitive) {
$query = "SELECT 1 FROM $table WHERE $column = :v COLLATE NOCASE LIMIT 1"; $query = "SELECT 1 FROM $table WHERE $column = :v COLLATE NOCASE LIMIT 1";
@ -104,8 +108,9 @@ function getSQLiteType($value): int
/** /**
* Log the given query string to the db debug log. * Log the given query string to the db debug log.
*/ */
function db_log($query) function db_log($query, $timeTaken = 0)
{ {
$GLOBALS['queries']++; $GLOBALS['queries']++;
if (env('debug', false)) $GLOBALS['query_log'][] = $query; $GLOBALS['query_time'] += $timeTaken;
if (env('debug', false)) $GLOBALS['query_log'][] = [$query, $timeTaken];
} }

View File

@ -36,5 +36,12 @@ function env_load($filePath)
*/ */
function env($key, $default = null) function env($key, $default = null)
{ {
return $_ENV[$key] ?? $_SERVER[$key] ?? (getenv($key) ?: $default); $v = $_ENV[$key] ?? $_SERVER[$key] ?? (getenv($key) ?: $default);
return match(true) {
$v === 'true' => true,
$v === 'false' => false,
is_numeric($v) => (int) $v,
is_float($v) => (float) $v,
default => $v
};
} }

View File

@ -171,7 +171,7 @@ function percent($num, $denom, $precision = 4): int
* the data is up to date with every request without having to query the database every use within, for example, a * the data is up to date with every request without having to query the database every use within, for example, a
* template. Will return false if the field does not exist, or the entire wallet array if no field is specified. * template. Will return false if the field does not exist, or the entire wallet array if no field is specified.
*/ */
function wallet($field = ''): array|int function wallet($field = '')
{ {
if (empty($GLOBALS['wallet'])) { if (empty($GLOBALS['wallet'])) {
$GLOBALS['wallet'] = db_query( $GLOBALS['wallet'] = db_query(
@ -188,9 +188,38 @@ function wallet($field = ''): array|int
/** /**
* Format an array of strings to a ul element. * Format an array of strings to a ul element.
*/ */
function array_to_ul(array $array) function array_to_ul($array)
{ {
$html = ''; $html = '';
foreach ($array as $item) $html .= "<li>$item</li>"; foreach ($array as $item) $html .= "<li>$item</li>";
return "<ul>$html</ul>"; return "<ul>$html</ul>";
} }
/**
* Start a keyed stopwatch to measure the time between two points in the code.
*/
function stopwatch_start($key)
{
if (!env('debug', false)) return;
$GLOBALS['stopwatch'][$key] = microtime(true);
}
/**
* Stop a keyed stopwatch. Stores the time in the global $stopwatch array under the key.
*/
function stopwatch_stop($key)
{
if (!env('debug', false)) return;
if (empty($GLOBALS['stopwatch'][$key])) return 0;
$GLOBALS['stopwatch'][$key] = microtime(true) - $GLOBALS['stopwatch'][$key];
}
/**
* Get the stopwatch value and format it to within 10 digits.
*/
function stopwatch_get($key)
{
if (!env('debug', false)) return;
if (empty($GLOBALS['stopwatch'][$key])) return 0;
return number_format($GLOBALS['stopwatch'][$key], 10);
}

View File

@ -24,7 +24,7 @@ const currently = [
* of overrides to set additional fields. A character's name must be unique, but this function does not check for * of overrides to set additional fields. A character's name must be unique, but this function does not check for
* that. Returns the created character's ID. * that. Returns the created character's ID.
*/ */
function char_create($user_id, $name, array $overrides = []): int function char_create($user_id, $name, $overrides = []): int
{ {
// Prep the data and merge in any overrides // Prep the data and merge in any overrides
$data = ['user_id' => $user_id, 'name' => $name]; $data = ['user_id' => $user_id, 'name' => $name];
@ -65,7 +65,7 @@ function char_location_create($char_id, $x = 0, $y = 0, $currently = 0)
* Create the character's gear table. A character's gear is where they store their equipped items. * Create the character's gear table. A character's gear is where they store their equipped items.
* @TODO: implement initial gear * @TODO: implement initial gear
*/ */
function char_gear_create($char_id, array $initialGear = []) function char_gear_create($char_id, $initialGear = [])
{ {
if (db_query(db_live(), "INSERT INTO char_gear (char_id) VALUES (:p)", [':p' => $char_id]) === false) { if (db_query(db_live(), "INSERT INTO char_gear (char_id) VALUES (:p)", [':p' => $char_id]) === false) {
throw new Exception('Failed to create character gear. (cgc)'); throw new Exception('Failed to create character gear. (cgc)');
@ -94,7 +94,7 @@ function char_count($user_id): int
/** /**
* Get a an array of id => [name, level] for all characters associated with an account ID. * Get a an array of id => [name, level] for all characters associated with an account ID.
*/ */
function char_list($user_id): array function char_list($user_id)
{ {
$stmt = db_query(db_live(), "SELECT id, name, level FROM characters WHERE user_id = :u", [':u' => $user_id]); $stmt = db_query(db_live(), "SELECT id, name, level FROM characters WHERE user_id = :u", [':u' => $user_id]);
if ($stmt === false) throw new Exception('Failed to list characters. (cl)'); if ($stmt === false) throw new Exception('Failed to list characters. (cl)');
@ -110,7 +110,7 @@ function char_list($user_id): array
/** /**
* Get a character's location info by their character ID. Returns the location's data as an associative array. * Get a character's location info by their character ID. Returns the location's data as an associative array.
*/ */
function char_get_location($char_id): array function char_get_location($char_id)
{ {
// Get the location // Get the location
$location = db_query(db_live(), "SELECT * FROM char_locations WHERE char_id = :p", [':p' => $char_id])->fetchArray(SQLITE3_ASSOC); $location = db_query(db_live(), "SELECT * FROM char_locations WHERE char_id = :p", [':p' => $char_id])->fetchArray(SQLITE3_ASSOC);

View File

@ -57,10 +57,3 @@ const item_qualities = [
5 => 'Excellent', 5 => 'Excellent',
6 => 'Masterwork', 6 => 'Masterwork',
]; ];
/**
* Create an item
*/
function create_item($name, array $type, array $opts) {
}

View File

@ -31,7 +31,7 @@ function page_layout($layout = '')
/** /**
* Shorthand to render a page with the current layout. * Shorthand to render a page with the current layout.
*/ */
function page($view, array $data = []) function page($view, $data = [])
{ {
return render("layouts/" . page_layout(), ['view' => "pages/$view"] + $data); return render("layouts/" . page_layout(), ['view' => "pages/$view"] + $data);
} }

View File

@ -7,7 +7,7 @@
* Example: * Example:
* `router_add($routes, 'GET', '/posts/:id', function($id) { echo "Viewing post $id"; });` * `router_add($routes, 'GET', '/posts/:id', function($id) { echo "Viewing post $id"; });`
*/ */
function router_add(array &$routes, $method, $route, callable $handler) function router_add(&$routes, $method, $route, $handler)
{ {
// Expand the route into segments and make dynamic segments into a common placeholder // Expand the route into segments and make dynamic segments into a common placeholder
$segments = array_map(function($segment) { $segments = array_map(function($segment) {
@ -33,7 +33,7 @@ function router_add(array &$routes, $method, $route, callable $handler)
* *
* @return array ['code', 'handler', 'params'] * @return array ['code', 'handler', 'params']
*/ */
function router_lookup(array $routes, $method, $uri): array function router_lookup($routes, $method, $uri)
{ {
// node is a reference to our current location in the node tree // node is a reference to our current location in the node tree
$node = $routes; $node = $routes;

View File

@ -1,5 +1,11 @@
<div id="debug-query-log"> <div id="debug-query-log">
<h3>Query Log</h3> <h3>Query Log</h3>
<p class="mb-2"><?= $GLOBALS['queries'] ?> queries were executed.</p> <p class="mb-2"><?= $GLOBALS['queries'] ?> queries were executed.</p>
<?php if (!empty($GLOBALS['query_log'])) foreach ($GLOBALS['query_log'] as $query) echo "<p>$query</p>"; ?> <?php
if (!empty($GLOBALS['query_log']))
foreach ($GLOBALS['query_log'] as $query) {
$time = number_format($query[1], 6);
echo "<p>({$time}s) {$query[0]}</p>";
}
?>
</div> </div>

View File

@ -0,0 +1,6 @@
<div id="debug-query-log">
<h3>Stopwatches</h3>
<p class="mb-2">Page execution took <?= number_format((microtime(true) - START_TIME), 10) ?> seconds.</p>
<p>Bootstrap: <?= stopwatch_get('bootstrap') ?> seconds</p>
<p>Router: <?= stopwatch_get('router') ?> seconds</p>
</div>

View File

@ -46,10 +46,16 @@
<footer> <footer>
<p>&copy; <?= date('Y') ?> Dragon Knight</p> <p>&copy; <?= date('Y') ?> Dragon Knight</p>
<p>q<?= $GLOBALS['queries'] ?></p> <p>q<?= $GLOBALS['queries'] ?></p>
<p>qt<?= number_format($GLOBALS['query_time'], env('debug', false) === true ? 6 : 2) ?></p>
<p>t<?= number_format((microtime(true) - START_TIME), env('debug', false) === true ? 6 : 2) ?></p>
<p>v<?= env('version') ?></p> <p>v<?= env('version') ?></p>
</footer> </footer>
<?php if (env('debug', false)) echo c_debug_query_log(); ?> <?php
if (env('debug', false)) {
echo c_debug_query_log();
}
?>
<script type="module"> <script type="module">
import Tooltip from '/assets/scripts/tooltip.js'; import Tooltip from '/assets/scripts/tooltip.js';