Hello Jan,

As I described in a recent report on using aFTP 1.51b with STinG, there is
an obvious problem in the design of the event loop, which makes efficient
use of aFTP for uploads impossible.  This latest version has even worse
abilities than previous ones, due to an absolute limitation to 1024 CPS
maximum.  Low BPS rates tend to hide this problem, but when the CPS rate
still remains 1024 at bitrates of 115200 BPS, it becomes intolerable.

Please note that I do not really blame you for this, since you are not
primarily developing for STinG/STiK.  You have never partcipated in
the STiK/STinG mailing lists, and so you have missed much information
that is valuable in programming clients/servers to use the STiK API
efficiently.

We recently discussed this in the STiK/STinG mailing list for beta
testers, and as a result of this discussion I decided to send you
this mail, so as to share with you the most important information
needed to make efficient use of the STiK API.


Let's start by looking at the behaviour of the current aFTP 1.51b,
to analyze the reason for its main problem, the 1024 CPS limit.

I think that aFTP has a low limit on the number of TCP_send calls per
evnt_loop (1?), with a fixed small send size, and a long timer event.
That is the only thing I can think of that would give so constant and
low transfer rate for all the bps rates that I have tried.

Simple arithmetic says that:
cps_rate_for_a_loop = sends_per_loop * send_size / loop_period

So assuming that send_size (except at EOF) and loop_period are constants,
the only way to get high upload speed is to _not_ limit sends_per_loop,
but let this climb to whatever the BPS used can handle.


Here is a method that I would recommend for any client APP:

First we have the event loop start, being of the normal 'forever' kind
(to be exited by break), or some other construct with similar effect.

Then we should record the current time, using a clock() call. This value
will be used later to see if the long loop time has been exceeded.
(Let's call this variable loop_start_time here.)

That is then followed by the usual kind of evnt_multi call scanning
for clicks, menu_events etc, including a _minimal_ timeout event.

Having a fairly long time interval for the main event work is no problem,
but it is wrong to use a long timer event to achieve it.  Instead a short
timer event should be used, just to allow immediate (almost) exit when no
other event type expected has occurred.

Then the main event work code should be executed, handling the stuff
that needs to be done once only per long loop period, and responding
to any non-timer events that were returned by the evnt_multi call.
(Menu, Window, Dialog, and other update stuff etc etc.)

After that there should be an inner loop, with a loop condition like
    "while ( (clock() - loop_start_time) < long_event_period )"

The inner loop should contain all the TCP_send and CN_get* calls and
other stuff that may need to be repeated more than once in the long
period, such as listening for server connections etc, but it should
_not_ contain any further loops. (Such would defeat the purpose.)
Instead they should rely on tests being repeated for each passage
of this inner loop.

Last inside the inner loop should be a call to _appl_yield(), the
minimum-delay AES call discussed many times before, which is also
defined by an assembly source and .H binding in most of my STinG
sources for Pure_C.  (See my NetD package for example.)
(See also NB_1: further below)

That call, placed in that position, ensures that each inner loop pass
allows other tasks to be unblocked, but without causing any task switch
before the loop code has been executed at least once.

And after that call both loops end, and so does my little example.


NB_1:
The '_appl_yield' function is a task switching function that has existed
in all TOS versions (yes, I do mean singletasking TOS), as well as in all
MagiC versions.  In fact no TOS-compatible implementation is complete if
this function is not available, or does not have the proper effect.

Unfortunately some MiNT-based multitaskers do not implement that function
correctly, so that it has little effect.  For those systems it may be
useful to call some other 'appl_yield' function instead of '_appl_yield',
and to implement the testing this requires in some local 'my_appl_yield'
function.  That is up to each client/server implementor.

The important thing is that the _real_ '_appl_yield' function is used in
processing loops for all systems that do support it, so as to avoid any
unnecessary blocking of other APPs or ACCs.  When used this way, it can
eliminate the need for long delays in evnt_timer and evnt_multi calls
without causing any unnecessary blocking.


The correct binding for '_appl_yield' is:
void    _appl_yield (void);  /* prototype for AES multitasking function */

That prototype should refer to an assembled module based on source that
contains the following subroutine:

_appl_yield:                    ;void    _appl_yield(void);
        move    #$C9,d0         ;d0 = XGEM opcode for _appl_yield function
        trap    #2              ;call XGEM function dispatcher
        rts                     ;return to caller

As you can see from the above, it is the only XGEM function that does not
require passing any of the AES or VDI arrays as parameters.  That is also
what makes it so valuable, since it allows implementation of delay loops
with cooperative multitasking even when the loops reside in a TSR, or some
other code that is not really a part of the current APP/ACC.
(The STinG kernel uses that feature internally too.)

Use of this (or equivalent) function is essential to efficient use of the
STiK API without blocking other tasks, and without losing the ability for
efficient bidirectional transfers.


If there is anything in the above text that you find unclear, or anything
else concerning the STinG/STiK API (or its usage) that you are in doubt
about, please don't hesitate to send me an Email.

