-
Notifications
You must be signed in to change notification settings - Fork 0
notes for refactoring the server implementation
The Server implementation (Server.sc) has become bloated over time [2]. What are the current problems?
(please feel free to edit!)
- One of the main problems seems to be the handling of ids, in particular those which have some kind of special status (e.g. "persistant ones").
- in terms of efficiency and design, we have a trade off between has-a and is-a
-
Iannis Zannos idea: It may be much more efficient to make server as subclass of NetAddr - in a sense it is just that. (But: then we cannot swap in a BundleNetAddr: s.bind / openBundle would have to be done with an if statement for every message send. This is still more efficient though. See [1]).
-
Depending on protocol, behavior may be different. Tim suggested: for TCP one should explicitly connect/disconnect. as it is based on connections, sending/receiving without a connection just doesn't make any sense.
-
This would be something to be done for NetAddr, too, probably best by passing the protocol type as a symbol.
here is a general design decision: Should the different behaviours be in one class or in several? We have:
- local/internal/remote (any reason not to drop internal support?)
- delayed(bundling)/instantaneous
- connected/connectionless
And where does it belong?
- If the server is a NetAddr, which should it be?
- Should a NetAddr know how to collect a bundle or is it the server's job? (I think the Addr should know it, one could simply give it a bundle instvar and add to it if it isn't nil)
- To really gain the efficiency, it would be necessary to modify the primitive
prNetAddr_SendMsg
(etc.) to add to the bundle if there is one.[3]
- platform dependent GUI instance variables need to be abstracted away (emacsbuf)
- allocators may need to go into one class, but the disadvantage is that this then needs delegation. Better keep them in the server.
- perhaps all state that is queried could go in a dictionary
- server options should be cached at boot time in order to have some information about the currently running server.
- or: server options should be queriable via OSC from the server (s.query).
- queryAllNodes shouldn't just post the info, but make it available as a string / unify with plotNodeTree ?
- the already refactored Volume is good, but quite complicated. If it is really that hard, it should be made a general technique not restricted to this class.
- bug: server calls volume.free, but Volume doesn't implement free.
- different server control (internal, local, remote). for local servers a subprocess should be used to manage the server life.
- (Q: couldn't this also help for remote servers? they would remain responsive even while a larger async task is going on).
- this might solve: s.hasShmInterface shouldn't return false after computer was sleep (bug).
- aliveThread should be a general object for this purpose, or better even for general purpose.
- recording should be moved outside: allow several instances of recorders with specified paths and busses
- recording currently simply uses one buffer outside the range (why does this work? probably some more buffers are allocated internally by the server application?). This looks like a hack.
- scoping should not be in server, but in a dedicated class.
- make explicit where things like record and volume nodes should be placed, and check if they can also be kept running on cmd-period.
- Possibly, it would make sense to introduce a second default group (e.g. "postprocessing group") that can contain everything that can be kept alive with no harm.
- cleanly configure cmd-period behaviour, so that nodes may be kept alive (e.g. recording) while still being able to reset the node id allocator.
- allocators should be able to manage node recovery
NetAddr has a:
- NetConnection (or none)
- bundle (or none)
Server (is a NetAddr) has a:
- ServerOptions (initial conditions)
- ServerState (updating) different classes for: local and remote (and maybe internal)
- Volume
- Recorder
ServerState could be a subclass of a general observer class that collects the status of an object by active lookup (using SkipJack). It could hold a dictionary.
[1] Comparing the efficiency between delegation and an if statement with a simple test, it turns out that the if statement is still 50% more efficient than a delegation to a second method (as it is now), and without he if statement it is only 55 % more efficient. (this is just a basic timing benchmark).
Test {
*redirect3 { |x, y, z|
this.prRedirect3(x, y, z)
}
*prRedirect3 { |x, y, z|
^x + y + z
}
*condition3 { |x, y, z|
if(x.isNil) { ^x + y + z };
^x + y + z
}
*redirect1 { |x|
this.prRedirect1(x)
}
*prRedirect1 { |x|
^x + 1
}
*condition1 { |x|
if(x.isNil) { ^x + 1 };
^x + 1
}
}
/*
bench { 100.do { Test.redirect3(1, 1, 1) } };
bench { 100.do { Test.condition3(1, 1, 1) } }; // about 50 % more efficient.
bench { 100.do { Test.prRedirect3(1, 1, 1) } };
bench { 100.do { Test.redirect1(1) } };
bench { 100.do { Test.condition1(1) } }; // about 50 % more efficient.
bench { 100.do { Test.prRedirect1(1, 1, 1) } }; // about 50 % more efficient.
*/
[2] Here is just the code that has the instance variables, to add comments about what may be handled where:
Server {
classvar <>local, <>internal, <default, <>named, <>set, <>program, <>sync_s = true;
var <name, <>addr, <clientID=0;
var <isLocal, <inProcess, <>sendQuit, <>remoteControlled;
var <serverRunning = false, <serverBooting = false, bootNotifyFirst = false;
var <>options, <>latency = 0.2, <dumpMode = 0, <notify = true, <notified=false;
var <nodeAllocator;
var <controlBusAllocator;
var <audioBusAllocator;
var <bufferAllocator;
var <scopeBufferAllocator;
var <syncThread, <syncTasks;
var <numUGens=0, <numSynths=0, <numGroups=0, <numSynthDefs=0;
var <avgCPU, <peakCPU;
var <sampleRate, <actualSampleRate;
var alive = false, booting = false, aliveThread, <>aliveThreadPeriod = 0.7, statusWatcher;
var <>tree;
var <window, <>scopeWindow;
var <emacsbuf;
var recordBuf, <recordNode, <>recHeaderFormat="aiff", <>recSampleFormat="float";
var <>recChannels=2;
var <volume;
var <pid;
var serverInterface;
var reallyDeadCount = 0;
[3] We can't make a branch in a call with a primitive:
// this won't compile
sendMsg { arg ... args;
bundle !? { bundle = bundle.add(args) };
_NetAddr_SendMsg
^this.primitiveFailed;
}
So it has to be done in this method (not sure how is best):
static int prNetAddr_SendMsg(VMGlobals *g, int numArgsPushed)
{
PyrSlot* netAddrSlot = g->sp - numArgsPushed + 1;
PyrSlot* args = netAddrSlot + 1;
big_scpacket packet;
int numargs = numArgsPushed - 1;
int error = makeSynthMsgWithTags(&packet, args, numargs);
if (error != errNone)
return error;
return netAddrSend(slotRawObject(netAddrSlot), packet.size(), (char*)packet.buf);
}