Cmdgfx new functionality and fixes as of version 0.999, part 2:Server functionality:
As mentioned earlier, cmdgfx can now run as a server
. In essence, it means that cmdgfx stays resident and runs in the background, waiting for input from stdin through a pipe. Thanks to TSnake41 for giving me this idea. Running as server has a number of advantages, mostly regarding speed:
1. On some machines/Windows versions, the overhead of running an external executable each frame (in this case cmdgfx) has a considerable overhead. By running as a server and taking input through the echo command, the overhead disappears.
2. Objects such as 3d models, images etc don't have to be read and parsed each frame, since they are resident (and already parsed) in memory after their first use
3. The batch script sending operations to cmdgfx does not need to wait for cmdgfx to finish before continuing. This asynchronicity increases speed, but it is a double-edged sword which also increases complexity and causes issues (see below)
4. The buffer does not start from blank each frame but lives on between runs. This makes it easier to make certain kind of effects and also makes it possible to progressively build a frame step by step.Basic setup:
In order to run cmdgfx as a server, it needs to be last in a pipe chain, such as:
Code: Select all
call program.bat | cmdgfx.exe "" S
The S flag tells cmdgfx to stay resident and run as server.
Since it's a bit cumbersome to have to write this every time, a better approach is for the script to call itself, like:
Sending operations to the server
Code: Select all
if defined __ goto :START
call %0 %* | cmdgfx "" S
Sending operations to the server looks very similar to running cmdgfx regularly, except it's done with the echo command, and all strings meant for cmdgfx should be prefixed with "cmdgfx:". As well as operations, flags can be set (or clerared, see info on flags below) as well as setting the palette for foreground and background.
Code: Select all
echo "cmdgfx: pixel 9 0 A 2,2" W10
Tells the server to draw an A and enable a waiting time per frame of 10ms
Any string sent to the server that is not prefixed with "cmdgfx:" is simply printed to stdout and otherwise ignored by the server.Exiting the server:
When the script ends, the server should be notified by sending:
If not, the server will complain when the client script ends. If this somehow causes problems, the complaint can be disabled by setting the 'e' flag in the server.Handling input:
While running as a server, it is still possible to read keys/mouse from the script using a separate call to cmdgfx (or another external program such as cmdwiz, GetInput etc). However as mentioned earlier, to maximize speed while using a server we want to avoid making any calls to external programs.
There are a few ways of doing this. The method I describe here has a disadvantage in that it reports input back to the script by writing to a file. While this works just fine on most drives, slow drives such as eMMC might get performance problem when writing to a file every frame when the update frequency is high. By using the 'O' flag instead of the 'o' flag, the file is only written when an event actually takes place, instead of every frame. Still, if a high key repeat is on or the mouse is moved continously, slow SSD/eMMC drives might be affected.
Anyway, to read e.g. keys using this method, start the server like:
Code: Select all
call program.bat | cmdgfx.exe "" SOk
The output from 'O' flag is written to the current folder as 'EL.dat'
(short for errorlevel), and as the name implies the file will simply contain the same form of ErrorLevel that cmdgfx would return if it was run separately.
To read it from within the script, use something like:
Code: Select all
if exist EL.dat set /p KEY=<EL.dat & del /Q EL.dat >nul 2>nul
set /a KEY=0
It's also recommended to delete EL.dat at the start of the script in case there is a leftover EL.dat in the folder which would then be seen as input.
There is another potential pitfall of the 'O' flag method: if the server runs faster than the script each frame, it might overwrite the existing EL.dat file before the script can read it, thus potentially missing input. In practice, this does not seem to be a big issue as far as I have seen, but it might happen...
To try to deal with both these problems (drive speed and possible missed events), the new separate executable cmdgfx_input.exe
can be used as an input server. I will write about cmdgfx_input in the next post.Asynchronicity and lag:
The client makes asynchronous calls to the server by using the echo command. In other words, the client keeps running immediately, without waiting for the server to finish rendering. What this actually means in practice is that each request is put in a queue in the buffer between the client and the server, and the server then attempts to finish these request as quick as possible (but within its timing contraints, for example if W10 has been set then the server will not render frames faster than 1 per 10ms)
The server is perfectly capable of timing how fast it can run at maximum with the W flag, but it is important to keep in mind that this is not true for the client script! Since we do not want to run any external command (this will affect speed badly), there is no control of how fast the client runs. If the client loop producing frames for the server is simple, it may very well execute much faster than the server, especially since the server is timing frames to keep a smooth frame rate. The problem with this is that the server will start lagging behind, which unfortunately also means that for example response to key presses will seem sluggish, since when the server informs the client of a key press, it may still have a long queue of images to render before it gets to the point where the key press actually changed something.
So how to (try to) deal with this? I'll start with the strangest but so far most successful method:
1. Fill up each call to the server with junk
Basically, there seems to be a limit to the size of the buffer between the client and the server. Someone else probably knows more about this, but essentially, it seems than when the buffer is full, the client will not run until the server has actually consumed some of the input on stdin. In effect, the lag is heavily diminished, because the client no longer "runs off" even if it executes its loop much faster than the server. This in turn means that input lag is reduced a lot.
Here is what I have done in several of the example server scripts:
2. The F flag
Code: Select all
:: Create 400 chars long string
set EXTRA=&for /l %%a in (1,1,100) do set EXTRA=!EXTRA!xtra
:: In every call to server, send 2000 extra characters per frame. 'skip' is used to make cmdgfx ignore the extra input
echo "cmdgfx: do something & skip %EXTRA% %EXTRA% %EXTRA% %EXTRA% %EXTRA%"
The F flag (which is only effective one frame) tells the server to flush the buffer and drop all following input that might have accumulated over stdin. While this decreases lag, it needs to be combined with the 1st method above if used, because otherwise the client might render frames way too fast and the movement will become jerky.
Also, if you want to use this flag to flush stdin right *now* it is actually counterproductive because the server has no knowledge of this wish until this line is processed, which may be several frames from now.3. Servercmd.dat
If you want to make sure that the next command run by the server is actually what you send *now*, you can write to a file called 'servercmd.dat' instead of sending strings into the queue over the pipe.
On each run, the server checks for the existence of this file in the current folder. If it exists, the server reads the file, and treats the input just as it would input over stdin. The only difference is that you should not include the prefix "cmdgfx:" in the string. The server then deletes the file 'servercmd.dat', before executing the operation(s) given in the file.
So, if for example, there was a need to flush the buffer as quick as possible to ensure a fast jump to the next "scene" or similar, this could be used in the script:
EDIT: It's worth noting that if there is no input available, cmdgfx server blocks until there is. In this case, writing to servercmd.dat will achieve nothing until the client places some output on stdout. In other words, to be completely sure that the above command would execute directly, it should actually be:
About flags:Persistent and non-persistent flags:
Code: Select all
echo "" F>servercmd.dat
echo "cmdgfx: "
When running cmdgfx externally, all flags are initially Off, and we might want to turn them On. However, when running as a server, flags may already be On and we might want to turn them Off at some point. To turn off a previously set flag, simply prefix the flag with - . E.g:
to disable the e flag.
However, some flags are "non-persistent", i.e. their "effect" last only one frame. These flags are 'c' (to take a a screenshot), 'F' (to flush the buffer), 'D' to clear objects, 'C' to reset frame counter, 'n' to produce no output, 'K' to wait for a key press. Also, the 'f' flag to set the buffer size cannot be disabled, but the buffer size/position can be changed.
If a flag is not persistent it has no effect to prefix it with -.Flags
' : run as server. Has no meaning if server is already running.
': As discussed in the previous section, F is used to flush the input buffer that the server uses (stdin). All unprocessed echoed operations already sent to the server will be flushed, i.e removed and ignored. This ensures that the server doesn't lag behind, but take note that if the script runs much faster than the server, motion will probably be jerky and uncontrolled.
': Since first being started, the server keeps track of how many frames it has rendered so far. Using this flag, you can reset the frame count to 0. At the moment, the only practical use for the frame counter is to write it out using the 'text' operation. Just include the text [FRAMECOUNT] in the string and it will be replaced while running by the actual frame count. It's useful for debugging and crude FPS measurements.
': This flag was discussed in the previous post (it saves the buffer to file as a screenshot). I'm mentioning it here because, again, it is important to think of the asynchronicity if using this with a server and the saved image should actually be used for something within the script.
For example, this will most likely not work:
Code: Select all
echo "cmdgfx: do stuff" c:0,0,100,100,1,10
copy capture-10.gxy test.gxy
because at the time the copy command is run, the server did not yet have the time to run the given operation and save the file, due to not running synchronously. In this case, you either have to wait for a while before copying (using e.g cmdwiz delay 500 or whatnot), or enter some sort of busy-wait loop with IF EXIST which ends whenever capture-10.gxy has actually been created.
' is used to clear all 3d objects from memory, so that they may be re-read from file. The main reason that would be desired is if a script occasionally dynamically re-creates one or more 3d object files with the same name as was previously used. In that case, the old object(s) must be cleared so that the new 3d object with the same name may actually be seen as a new file and not re-used from memory. There is currently no way to erase only a single 3d object from memory.
': using the server, it is possible to build a frame gradually in the buffer before finally showing it. In order to do this, set the 'n' flag to disable output, and also disable any w or W flags that are active. When ready to show the image, set the W flag again. Building a frame gradually may sometimes be easier, and in some cases more efficient as it can save you from building very long strings for a single cmdgfx call (having very long strings in environment variables is bad for batch performance)
' flag is another more crude method of dealing with the asynchronous lag problem. What it does is to immediately flush the stdin buffer if there was something written to EL.dat, i.e. if an input event happened. This will improve key response time, but it has the disadvantage that there might be a visible "jump" when the server suddenly skips a bunch of frames. This would most likely not be acceptable in e.g. an action game, but might be ok for some applications.
Ok, that was way too much text that only very few will ever read... Anyway, finished soon...
Oh, and to add, I think the easiest way to understand this stuff is just to try out the different server examples and poke around with them