[PD-dev] startup order for pd/pd-gui
Hans-Christoph Steiner
hans at eds.org
Wed Jan 28 23:57:51 CET 2009
This is a great document. I wikified it:
http://puredata.info/docs/developer/PdStartupOrder
.hc
On Jan 10, 2009, at 3:10 PM, Martin Peach wrote:
> Here we will trace the flow of control through the various parts of
> pd as pd starts up.
>
> *** pd / pd.exe ***
> PD starts in s_entry.c, where two types of main function, one for
> MSW and the other for Unix
> (linux and OSX (and Cygwin, we hope)), call sys_main(argc, argv),
> which is in s_main.c.
> On Windows, s_entry.c is the only file compiled into pd.exe. The
> rest ends up in pd.dll, which is linked
> to pd.exe. This is done so that externs will be able to link back to
> functions defined in pd.
> main() in s_entry.c calls:
> |
> |-sys_main in s_main.c calls:
> |
> |-pd_init(): in m_pd.c, calls:
> | |
> | |-mess_init(): in m_class.c sets up the builtin symbols
> s_pointer, s_float,
> | | s_symbol, s_bang, s_list, s_anything, s_signal, s__N, s__X,
> | | s_x, s_y, and s_, as well as the classes pd_objectmaker,
> | | for making boxes and pd_canvasmaker for making canvases.
> | | pd_canvasmaker is bound to the symbol s__N (#N, i.e. a new
> window).
> | | pd_objectmaker gets a pointer to new_anything().
> | |
> | |-obj_init(): in m_obj.c sets up a generic inlet_class and adds
> pointers to
> | | inlet_bang, inlet_pointer, inlet_float, inlet_symbol,
> inlet_list,
> | | and inlet_anything.
> | | Also sets up specific pointerinlet_class, floatinlet_class, and
> | | symbolinlet_class.
> | |
> | |-conf_init(): in m_conf.c calls setup functions for the builtin
> objects.
> | | m_pd_setup() sets up the bindlist class that allows the binding
> | | of several objects to the same symbol, the way
> pointerinlet_class,
> | | floatinlet_class etc. are all bound to the symbol "inlet"
> | |
> | |-glob_init(): in m_glob.c sets up a pd class to respond to
> messages like "open", "quit", and the
> | symbols for the audio and MIDI dialogs, (and watchdog on Non-
> MacOSX unix).
> |
> |-sys_findprogdir(argv[0]): in s_main.c finds the path to the pd
> program, and from there
> | decides what sys_libdir and sys_guidir should be.
> |
> |-sys_loadpreferences(): in sfile.c, loads a file named .pdsettings
> from the user's home directory
> | (Unix), or (Windows) gets preferences from the registry.
> |
> |-sys_rcfile(): (non-Windows only) in s_path.c, attempts to load
> and parse a file named ".pdrc" (STARTUPNAME)
> |
> |-sys_argparse(): in s_main.c attempts to parse the command line
> arguments in the same way as
> | the .pdrc file. (sys_rcfile also calls sys_argparse).
> |
> |-sys_afterargparse(): in s_main.c sets the midi and ausio
> parameters according to the preferences
> | | that were loaded from .pdrc and the command line. Calls:
> | |
> | |-sys_open_audio(): in s_audio.c: opens audio in platform-
> specific manner
> | |
> | |-sys_open_midi(): in s_midi.c: opens midi in platform-specific
> manner
> |
> |-sys_startgui(sys_guidir->s_name): in s_inter.c sets up some
> signal handlers, opens a TCP socket (sys_guisock)
> | | on port 5400 (FIRSTPORTNUM) and then forks.
> | | *The child starts the gui process:
> | | *Under unix, the environment variables TCL_LIBRARY and
> TK_LIBRARY are declared
> | | *and the pd-gui program is called with the paths to tcl and tk
> libs, the pd.tk script and the port number as arguments:
> | | */bin/sh sh -c TCL_LIBRARY="LIBDIRPATH/tcl/library"
> TK_LIBRARY="LIBDIRPATH/tk/library" "GUIDIRPATH/pd-gui" 5400
> | | *where LIBDIRPATH = sys_libdir->s_name, usually /lib/pd, and
> GUIDIRPATH = guidir, usually /lib/pd/bin
> | | *On Windows and MacOSX, the Wish shell is spawned and fed the
> pd.tk script.
> | | *The child exits, doesn't return.
> | | *On non-MacOSX unix platforms the parent thread opens a pipe,
> then forks again:
> | | **The child gives itself a higher priority by calling
> sys_set_priority(1) and
> | | **calls the shell to execute pd-watchdog. From the code:
> | | **"To prevent lockup, we fork off a watchdog process with
> higher real-time priority than ours. The GUI has to send
> | | **a stream of ping messages to the watchdog THROUGH the Pd
> process which has to pick them up from the GUI and forward
> | | **them. If any of these things aren't happening the watchdog
> starts sending "stop" and "cont" signals to the Pd process
> | | **to make it timeshare with the rest of the system. (Version
> 0.33P2 : if there's no GUI, the watchdog pinging is done
> | | **from the scheduler idle routine in this process instead.)"
> | | **The child exits.
> | | *The parent sets itself to another priority by calling
> sys_set_priority(0).
> | | *It assigns the write end of the pipe to sys_watchfd.
> | | *On Windows and MacOSX the parent uses system-specific calls to
> set a higher priority for itself.
> | | *Finally the parent thread calls
> | |-socketreceiver_new(0, 0, 0, 0) in s_inter.c. This initializes a
> t_socketreceiver class with all fields zeroed.
> | | struct _socketreceiver
> | | {
> | | char *sr_inbuf;
> | | int sr_inhead;
> | | int sr_intail;
> | | void *sr_owner;
> | | int sr_udp;
> | | t_socketnotifier sr_notifier;
> | | t_socketreceivefn sr_socketreceivefn;
> | | }
> | |
> | |-sys_addpollfn(sys_guisock, (t_fdpollfn)socketreceiver_read,
> sys_socketreceiver) in s_inter.c. This adds socketreceiver_read to
> | | the list of functions checked during sys_domicrosleep()
> | | typedef struct _fdpoll
> | | {
> | | int fdp_fd;
> | | t_fdpollfn fdp_fn;
> | | void *fdp_ptr;
> | | } t_fdpoll;
> | | socketreceiver_read(t_socketreceiver *x, int fd) in s_inter.c
> attempts to read data from sys_guisock.
> | |
> | |
> | | *Non-MacOSX unix platforms call:
> | |-sys_gui("pdtk_watchdog\n"), this calls the pdtk_watchdog
> procedure in pd.tk
> | |
> | |-sys_get_audio_apis(buf);
> | |
> | |-sys_vgui("pdtk_pd_startup {%s} %s\n", pd_version, buf):
> pdtk_pd_startup is in pd.tk. It calls pd
> | with the pd command and the argument init.
> | *The class pd has glob_initfromgui() as the handler for the
> init message.
> |
> | If sys_externalschedlib is zero (always(?) ...looks like a
> provision for an external scheduler is partly implemented), all
> versions call:
> |-sys_reopen_midi(): in s_midi.c opens midi by calling:
> | |
> | |-sys_open_midi() (again...)
> |
> |-sys_reopen_audio(): in s_audio.c opens audio by calling:
> | |
> | |-sys_open_audio() (again...)
> |
> |-m_scheduler(): in m_sched.c is the main loop, calls:
> |
> |-sys_clearhist(): in m_sched.c clears the real-time execution
> histogram.
> |
> |-sys_initmidiqueue(): in s_midi.c zeroes some timers.
> | , then goes into a loop until the int sys_quit has been set
> (although pd can exit() at other places
> | when signaled).
> | In the loop we have, mainly:
> |
> |-sys_send_dacs(): in s_audio.c uses platform-specific code to
> acquire a block of adc and send
> | a block of dac numbers
> |
> |-sys_getrealtime(): in s_inter.c returns the number of seconds
> since pd startup.
> |
> |-sys_pollmidiqueue(): in s_midi.c calls platform-dependent midi
> code for midi I/O.
> |
> |-sys_poll_gui(): in s_inter.c calls:
> | |
> | |-sys_domicrosleep(): in s_inter.c checks for received data on
> the gui socket sys_guisock (...or any functions added with
> sys_addpollfn())
> | |
> | |-sys_poll_togui() which calls:
> | |
> | |-sys_flushtogui(): sends queued qui stuff through sys_guisock
> | |
> | |-sys_flushqueue(): updates the guiqueue and calls
> sys_flushtogui()
> | |
> | |-sys_reportidle(): in s_audio.c is empty(?).
> |
> |-sys_microsleep(): in s_inter.c calls:
> |-sys_domicrosleep() in s_inter.c checks for received data on
> the gui socket sys_guisock (...or any functions added with
> sys_addpollfn())
>
> *** pd-gui / pdtcl.dll / pdtcl *** the graphical user interface ***
> On all platforms, the gui is compiled from t_main.c and t_tkcmd.c.
> Under unix, pd-gui is an executable program. It is started by a
> child thread of pd in sys_startgui() in s_inter.c.
> Under Windows, pdtcl.dll is a shared library. It is loaded when the
> Wish shell processes the pd.tk script.
> On MacOSX, pdtcl is the name of the library loaded in pd.tk.
> main() in t_main.c calls:
> |
> |-pdgui_setname(argv[0]): in t_tkcmd.c seems to set the global
> pdgui_path to the first part of the path to pd-gui.
> |
> |-pdgui_setsock(argv[1]): in t_tkcmd.c with the port number. Sets
> pd_portno = 5400 and returns.
> |
> |-pdgui_sethost(argv[2]): in t_tkcmd.c with the host name if one was
> given (probably never happens)
> |
> |-Tk_Main(): in tk.lib is the main entry point of the tk library.
> Called with arguments "Pd" and "Tcl_AppInit", it starts the tk
> interpreter and calls
> |
> |-Tcl_AppInit(): in t_main.c, with pointer to the interpreter.
> | (Some of the following is from the Tcl/Tk documentation at http://www.tcl.tk/man/tcl8.4/)
> | Tcl_AppInit is a "hook" procedure that is invoked by the main
> programs for Tcl applications such as tclsh and wish.
> | Its purpose is to allow new Tcl applications to be created
> without modifying the main programs provided as part of
> | Tcl and Tk. To create a new application you write a new version
> of Tcl_AppInit to replace the default version
> | provided by Tcl, then link your new Tcl_AppInit with the Tcl
> library.
> | TclAppInit calls
> |
> |-Tcl_Init(interp): In libtcl. Tcl_Init is a helper procedure
> that finds and sources the init.tcl script, which should exist
> | somewhere on the Tcl library path. On Macintosh systems, it
> additionally checks for an Init
> | resource and sources the contents of that resource if
> init.tcl cannot be found.
> |
> |-Tk_Init(interp): In libtk. Tk_Init is the package
> initialization procedure for Tk.
> | It is normally invoked by the Tcl_AppInit procedure for an
> application or by the load command.
> | Tk_Init adds all of Tk's commands to interp and creates a new
> Tk application, including its main window.
> | If the initialization is successful Tk_Init returns TCL_OK;
> if there is an error it returns TCL_ERROR.
> | Tk_Init also leaves a result or error message in interp-
> >result.
> | If there is a variable argv in interp, Tk_Init treats the
> contents of this variable as a list of
> | options for the new Tk application. The options may have any
> of the forms documented for the wish
> | application (in fact, wish uses Tk_Init to process its
> command-line arguments).
> |
> |-pdgui_startup(interp): In t_tkcmd.c. Sets tk_pdinterp=interp.
> Calls:
> |
> |-Tcl_CreateCommand(): In libtcl. Creates a command "pd"
> implemented by pdCmd()
> | Windows versions also create a command "pd_pollsocket"
> implemented by pd_pollsocketCmd().
> |
> |-pdgui_setupsocket(): in t_tkcmd.c Calls
> | |
> | |-pdgui_connecttosocket(): in t_tkcmd.c sets up a client socket
> | | (AF_INET, SOCK_STREAM, 0) on port 5400 and connects it.
> (Windows only or when pd_portno is defined)
> | |
> | |-pd_startfromgui(): in t_tkcmd.c when portno is not defined
> (usually).
> | |
> | | Sets up a client socket on the first available port
> starting at 5400 and connects it.
> | | Unix versions call
> | |
> | |-Tk_CreateFileHandler(sockfd, TK_READABLE | TK_EXCEPTION,
> pd_readsocket, 0);
> | Tk_CreateFileHandler() is Tcl_CreateFileHandler() and
> is only implemented on unix. Windows versions have to poll tk instead.
> | Tcl_CreateFileHandler with TK_READABLE | TK_EXCEPTION
> arranges for pd_readsocket() to be invoked in the future whenever
> reading
> | becomes possible on the 'file' sockfd or an exceptional
> condition exists for the file.
> | The callback to pd_readsocket() is made by
> Tcl_DoOneEvent, so Tcl_CreateFileHandler is only useful in programs
> that dispatch
> | events through Tcl_DoOneEvent or through Tcl commands
> such as vwait.
> | pd_readsocket(): in t_tkcmd.c parses data received on
> the csocket and calls
> | |
> | |-tcl_mess(): in t_tkcmd.c calls
> | |
> | |-Tcl_Eval(): in libtcl, handles the data from pd as
> a tcl script.
> |
> |-pdgui_evalfile("pd.tk"): (non-MacOSX unix only, the others
> use Wish to do this and so pd.tk is already being processed: that's
> how we got here)
> | in t_tkcmd.c calls
> |
> |-pdgui_doevalfile(): in t_tkcmd.c calls
> |
> |-Tcl_EvalFile(): in libtcl which parses the pd.tk script.
>
> ***
> *** pd.tk ***
> The Tcl/tk script pd.tk is a simple copy of u_main.tk.
>
> Tcl/tk is Tool Command Language with the Tool Kit extension for
> graphical interfaces.
> (http://www.tcl.tk)
> The first line (#!/usr/bin/wish) invokes the wish shell.
> Wish is a simple program comprising the Tcl command language, the Tk
> toolkit,
> and a main program that reads commands from standard input or from a
> file.
> It makes a main window, then processes Tcl commands in the script
> until all windows have been deleted.
>
> The next lines set the global variable pd_nt to 0, 1, or 2 depending
> on the platform (default=0, Windows=1, Darwin=2).
> The global variable pd_tearoff is set to 1.
>
> On Windows, the next little block loads pdtcl.dll. It gets the path
> by taking the first argument to wish (the path to the script),
> clipping off the script name, replacing backslashes with forward
> slashes, appending "/bin/pdtcl.dll" to the path,
> and finally loading it. This will cause tcl to call Tcl_AppInit() in
> t_main.c and set up the tcl part of the pd application.
>
> On MacOSX (Darwin) a similar procedure loads pdtcl with a similar
> result. Here we also set up a drag-and-drop handler.
>
> On unix, the main pd-gui program is already running and has called
> Tcl_EvalFile() to parse this script.
>
> Under Windows, the command pd_pollsocket is executed every 20
> milliseconds.
> pd_pollsocketCmd() calls pd_readsocket(). At line 3079 of pd.tk we
> have:
> ############# start a polling process to watch the socket
> ##############
> # this is needed for nt, and presumably for Mac as well.
> # in UNIX this is handled by a tcl callback (set up in t_tkcmd.c)
>
> if {$pd_nt == 1} {
> proc polleofloop {} {
> pd_pollsocket
> after 20 polleofloop
> }
>
> polleofloop
> }
> The value of pd_nt is 1 only on Windows machines. This declares a
> procedure named polleofloop and then calls it.
> polleofloop invokes the pd_pollsocket command that was defined in
> pdgui_startup() in t_tcmd.c, i.e. it calls pd_pollsocketCmd(), which
> in turn calls pd_readsocket(). The procedure then sleeps for 20
> milliseconds and calls itself again.
>
> The ################## set up main window #########################
> part:
>
> The menu command creates a new top-level window whose path is
> ".mbar" and makes it into a menu widget.
>
> The canvas command creates a new window whose path is ".dummy" and
> makes it into a canvas widget.
> It is given a height of 2p (printer's points: 1/72")and a width of
> 6c (centimeters).
>
> The frame command creates a new window whose path is ".controls" and
> makes it into a frame widget.
> A frame is a simple widget. It is used as a spacer or container for
> complex window layouts.
> A frame has a background color and an optional 3-D border to make
> the frame appear raised or sunken.
>
> The pack command packs .controls and .dummy at the top of the window
> and stretches them to fit the width of the window.
>
> Menus are added to the menu bar: file, find, windows, audio. The
> windows menu is setup with -postcommand [concat pdtk_fixwindowmenu],
> which has the effect of calling the proc pdtk_fixwindow menu before
> the menu is posted. pdtk_fixwindowmenu fills in the windows menu
> with a list of open windows.
>
> Next the checkbuttons audiobutton and meterbutton are added to
> the .controls.switches frame and bound to the pd command as dsp and
> meters, resp.
> Buttons are added to .controls.inout to reflect the value of
> ctrls_inlevel and ctrls_outlevel.
> A button .controls.dio is connected to the pd command as
> audiostatus. They are all packed together.
>
> A frame .printout with vertical scrollbar is then added and packed.
> A proc pdtk_post is declared to print text in this frame.
> pdtk_post is invoked from dopost() in s_print.c.
>
> The control key and control-shift combination are bound to
> pdtk_pd_ctrlkey.
> On MacOSX the Mod1 key and Mod1-shift are bound to
> pdtk_canvas_ctrlkey.
>
> The window is titled "pd". It's path/name is "."
> The menu bar is set to 200 wide by 150 high.
> The menus are populated with items and procs are defined to
> correspond to each item.
>
> Usually, clicking on a menu item results in a message being sent to
> pd using the pd command.
>
> Procedures in pd.tk can also be called from pd via sys_vgui() in
> s_inter.c.
> For instance, dopost() in s_print.c calls pdtk_post in pd.tk to
> print messages.
> sys_vgui() adds messages to a queue named sys_guibuf. This is sent
> via TCP through sys_guisock
> whenever sys_flushtogui() is called, which is only from
> sys_poll_togui(), which is only called by
> sys_pollgui(), which may be called from sched_tick() and
> m_scheduler() in m_sched.c. sched_tick()
> is only called from m_scheduler(), which is called from sys_main()
> in s_main.c as part of the main loop.
> The messages are received by pd_readsocket() which calls tcl_mess()
> with each complete command.
> tcl_mess() calls Tcl_Eval() in libtcl to evaluate the command.
>
> proc pdtk_ping:
> To stop the pd-gui from choking on too much data arriving too fast,
> sys_flushqueue() calls pdtk_ping whenever
> more than GUI_BYTESPERPING (1024) bytes are sent in one go. Then
> sys_flushqueue() waits for pd-gui to send pd the 'ping' message,
> which is
> handled by glob_ping() in s_inter.c. glob_ping() simply resets the
> global variable sys_waitingforping, so that sys_flushqueue()
> can resume sending data.
>
> proc pdtk_watchdog:
> proc pdtk_watchdog {} {
> pd [concat pd watchdog \;]
> after 2000 {pdtk_watchdog}
> }
> On non-MacOSX unix platforms this is called from sys_startgui().
> Every two seconds it will call pd with the 'watchdog' message.
> This is handled by glob_watchdog() in s_inter.c. glob_watchdog()
> sends an ASCII carriage return through sys_watchfd.
>
> *** pd-watchdog ***
> s_watchdog.c is the source of pd-watchdog, which was spawned in
> sys_startgui(). pd-watchdog monitors its stdin for
> any input, timing out after 5 seconds if nothing was received. In
> this case it kills pd with a SIGHUP, but normally it will
> continue looping as long as characters are received from pd at a
> rate of at least one every 5 seconds. So if pdtk_watchdog
> calls pd every 2 seconds and glob_watchdog() sends a CR each time
> through the pipe, everything runs smoothly.
> ***
----------------------------------------------------------------------------
All mankind is of one author, and is one volume; when one man dies,
one chapter is not torn out of the book, but translated into a better
language; and every chapter must be so translated.... -John Donne
More information about the Pd-dev
mailing list