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
*/
// [code, handler, params]
stopwatch_start('router');
$l = router_lookup($r, $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
stopwatch_stop('router');
if ($l['code'] !== 200) router_error($l['code']);
$l['handler'](...$l['params'] ?? []);
@ -46,3 +49,8 @@ $l['handler'](...$l['params'] ?? []);
Cleanup
*/
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']);
}
}
/**
* 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/auth.php';
// Track the start time of the request
define('START_TIME', microtime(true));
/*
Load env, set error reporting, etc.
*/
@ -34,14 +37,19 @@ if (env('debug') === 'true') {
error_reporting(E_ALL);
}
stopwatch_start('bootstrap'); // Start the bootstrap stopwatch
// Generate a new CSRF token. (if one doesn't exist, that is)
csrf();
// Have a global counter for queries
// Have global counters for queries
$GLOBALS['queries'] = 0;
$GLOBALS['query_time'] = 0;
// Set the default page layout
page_layout('basic');
// Run auth_check to see if we're logged in, since it populates the user data in SESSION
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.
*/
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]);
}
@ -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"';
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()
{
auth_only(); must_have_character();
auth_only_and_must_have_character();
$GLOBALS['active_nav_tab'] = 'chars';
echo page('chars/list', ['chars' => char_list(user('id'))]);
@ -16,7 +16,7 @@ function char_controller_list_get()
*/
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';
@ -68,7 +68,7 @@ function char_controller_list_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);

View File

@ -3,7 +3,7 @@
/**
* Open a connection to a database.
*/
function db_open($path): SQLite3
function db_open($path)
{
$db = new SQLite3($path);
@ -20,7 +20,7 @@ function db_open($path): SQLite3
/**
* Return a connection to the auth database.
*/
function db_auth(): SQLite3
function db_auth()
{
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.
*/
function db_live(): SQLite3
function db_live()
{
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.
*/
function db_fights(): SQLite3
function db_fights()
{
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.
*/
function db_blueprints(): SQLite3
function db_blueprints()
{
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
* 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);
if (!empty($params)) foreach ($params as $key => $value) $stmt->bindValue($key, $value, getSQLiteType($value));
db_log($query);
return $stmt->execute();
$start = microtime(true);
$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.
*/
function db_exec(SQLite3 $db, $query)
function db_exec($db, $query)
{
db_log($query);
return $db->exec($query);
$start = microtime(true);
$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
* 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) {
$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.
*/
function db_log($query)
function db_log($query, $timeTaken = 0)
{
$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)
{
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
* 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'])) {
$GLOBALS['wallet'] = db_query(
@ -188,9 +188,38 @@ function wallet($field = ''): array|int
/**
* Format an array of strings to a ul element.
*/
function array_to_ul(array $array)
function array_to_ul($array)
{
$html = '';
foreach ($array as $item) $html .= "<li>$item</li>";
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
* 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
$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.
* @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) {
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.
*/
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]);
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.
*/
function char_get_location($char_id): array
function char_get_location($char_id)
{
// Get the location
$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',
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.
*/
function page($view, array $data = [])
function page($view, $data = [])
{
return render("layouts/" . page_layout(), ['view' => "pages/$view"] + $data);
}

View File

@ -7,7 +7,7 @@
* Example:
* `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
$segments = array_map(function($segment) {
@ -33,7 +33,7 @@ function router_add(array &$routes, $method, $route, callable $handler)
*
* @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 = $routes;

View File

@ -1,5 +1,11 @@
<div id="debug-query-log">
<h3>Query Log</h3>
<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>

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>
<p>&copy; <?= date('Y') ?> Dragon Knight</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>
</footer>
<?php if (env('debug', false)) echo c_debug_query_log(); ?>
<?php
if (env('debug', false)) {
echo c_debug_query_log();
}
?>
<script type="module">
import Tooltip from '/assets/scripts/tooltip.js';