<?php
/*
class: PHP NonBlocking CLI - Plugin based non-blocking CLI class for PHP console programs
version: 1.0
url: https://www.phpclasses.org/package/12994-PHP-A-non-blocking-CLI-class-for-PHP-console-programs.html
author: https://www.phpclasses.org/browse/author/144301.html
license: BSD License (3-clause BSD) https://opensource.org/licenses/BSD-3-Clause
nb_cli: main controller class
nb_plugin: abstract class for plugins (You would then extend this class to make your own plugins for nb_cli)
short description:
A non-blocking CLI class for PHP console programs.
long description:
This class allows for non-blocking CLI input and output using plugins to contain the external functionality.
The main loop runs at a set speed, and each plugin can run at its own speed (but within the speed of the main loop).
The main controller can interact with the user.
The plugins can also interact with the user, and are intended to be the primary interaction with the user.
Plugins are only aware of themselves, and have no knowledge of other plugins, or the main controller.
note: THIS CLASS IS INTENDED FOR COMMAND LINE RUN PHP. Run an example from the command line to see it in action.
eg) php test-cli.example.php
eg) php test-cli.example.game.php
example ideas for programs that could potentially benefit from this class:
- a MUD (multi-user dungeon) game
- realtime text based rpg game
- game engine
- interactive server apps
- etc...
quick example:
// ---
// step 1: create a plugin
class my_plugin extends nb_plugin {
private $stdin;
private $stdout;
// handler for running multiple loops at different intervals //
// so we can give this plugin some loop independence (run on their own timers)
private $next_run = []; // when a loop should run // An array so we can have multiple loops.
private $loop_running = []; // so we don't engage another loop until the other is finished processing
public function __construct() {
// Set non-blocking mode for STDOUT
stream_set_blocking(STDOUT, false);
// Set non-blocking mode for STDIN
stream_set_blocking(STDIN, false);
$this->stdin = fopen('php://stdin', 'r');
$this->stdout = fopen('php://stdout', 'w');
}
public function out($msg) {
fwrite($this->stdout, $msg);
}
public function handleCommand($command) {
// handle incoming commands
switch ($command) {
case 'test':
$this->out("Test command\n");
break;
}
}
public function welcome() {
// welcome message
$this->out("-- NonBlockingCLI test server --\n");
$this->out("Welcome to the NonBlockingCLI test server.\n");
}
public function run() {
// called every loop
$this->loop_1();
$this->loop_2();
}
private function loop_1()
{
// This value will be used to set the pace of this loop.
$loop_timer = 3.0; // seconds // how often to run this loop // 1.0 = 1 second, 1.5 = 1.5 seconds, etc.
// set a key for this loop to store its timing values like next_run and loop_running
$loop_key = 1; // for $this->next_run and $this->loop_running
// we want to take a slot in next_run in case we have other loops.
if (!isset($this->next_run[$loop_key])) {
$this->next_run[$loop_key] = 0.0; // initialize
$this->loop_running[$loop_key] = false;
}
// check if we should run this time
if ($this->loop_running[$loop_key] || microtime(true) < $this->next_run[$loop_key]) return;
// set loop as running
$this->loop_running[$loop_key] = true;
// BEGIN --> do stuff
$output = "Current time: " . date('Y-m-d H:i:s') . "\n";
$this->out($output);
// END --> do stuff
// set pace of plugin loop (runs independent of main loop)
// store our next run time in a variable
$this->next_run[$loop_key] = microtime(true) + $loop_timer; // 1 second = 1.0, 1.5 seconds = 1.5, etc.
// set loop as not running
$this->loop_running[$loop_key] = false;
}
private function loop_2()
{
// This value will be used to set the pace of this loop.
$loop_timer = 5.0; // seconds // how often to run this loop // 1.0 = 1 second, 1.5 = 1.5 seconds, etc.
// set a key for this loop to store its timing values like next_run and loop_running
$loop_key = 2; // for $this->next_run and $this->loop_running
// we want to take a slot in next_run in case we have other loops.
if (!isset($this->next_run[$loop_key])) {
$this->next_run[$loop_key] = 0.0; // initialize
$this->loop_running[$loop_key] = false;
}
// check if we should run this time
if ($this->loop_running[$loop_key] || microtime(true) < $this->next_run[$loop_key]) return;
// set loop as running
$this->loop_running[$loop_key] = true;
// BEGIN --> do stuff
$output = "Tick\n";
$this->out($output);
// END --> do stuff
// set pace of plugin loop (runs independent of main loop)
// store our next run time in a variable
$this->next_run[$loop_key] = microtime(true) + $loop_timer; // 1 second = 1.0, 1.5 seconds = 1.5, etc.
// set loop as not running
$this->loop_running[$loop_key] = false;
}
}
// ---
// step 2: create main controller
// create main controller
$cli = new nb_cli();
// step 3: add plugin to main controller
$plugin = new my_plugin();
// add plugin to main controller
$cli->add_plugin($plugin);
// step 4: run main controller welcome() after setting up plugins
// welcome message
$cli->welcome();
// step 5: run main controller run() which starts main loop
// optional: set speed of main loop
$cli->looptime = 100000; // microseconds between loops. 100000 = 0.1 seconds
// run main loop which runs until user types 'quit'. Runs plugin loops as well.
$cli->run();
*/
# when we make a plugin, we have to make sure it contains these functions:
abstract class nb_plugin {
// help: abstract class makes sure that all plugins have these functions
abstract public function handleCommand($command); // handle incoming commands
abstract public function welcome(); //
abstract public function run(); // called every loop
}
// this class is the main controller that passes messages to the plugins.
class nb_cli {
private $stdin;
private $stdout;
// main loop speed
public $looptime = 100000; // microseconds between loops. 100000 = 0.1 seconds
// plugins
private $plugins = array();
public function __construct() {
// Set non-blocking mode for STDOUT
stream_set_blocking(STDOUT, false);
// Set non-blocking mode for STDIN
stream_set_blocking(STDIN, false);
// ability for main loop to interact with user
$this->stdin = fopen('php://stdin', 'r');
$this->stdout = fopen('php://stdout', 'w');
}
public function out($msg) {
fwrite($this->stdout, $msg);
}
public function add_plugin(nb_plugin $plugin) {
$this->plugins[] = $plugin;
}
public function welcome() {
// process plugins
foreach ($this->plugins as $plugin) {
$plugin->welcome();
}
}
public function run() {
while (true) {
// Process user commands for main loop
if (($command = fgets($this->stdin)) !== false) {
$command = trim($command);
$this->handleCommand($command);
}
// Process plugins
foreach ($this->plugins as $plugin) {
$plugin->run();
}
// Sleep for a while
usleep($this->looptime); // main loop runs every 100000 microseconds = 0.1 seconds
}
}
private function handleCommand($command) {
// pass command to plugins
foreach ($this->plugins as $plugin) {
$plugin->handleCommand($command);
}
// basically main loop just handling a 'quit' command
switch ($command) {
case 'quit':
exit();
break;
}
}
}
?>
|