PreviousUpNext

15.4.1411  src/lib/x-kit/widget/edit/millboss-imp.pkg

## millboss-imp.pkg
#
# See overview comments in:
#
#     src/lib/x-kit/widget/edit/millboss-imp.api

# Compiled by:
#     src/lib/x-kit/widget/xkit-widget.sublib


stipulate
    include package   threadkit;                                # threadkit                     is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
    #
#   package ap  =  client_to_atom;                              # client_to_atom                is from   src/lib/x-kit/xclient/src/iccc/client-to-atom.pkg
#   package au  =  authentication;                              # authentication                is from   src/lib/x-kit/xclient/src/stuff/authentication.pkg
#   package cpm =  cs_pixmap;                                   # cs_pixmap                     is from   src/lib/x-kit/xclient/src/window/cs-pixmap.pkg
#   package cpt =  cs_pixmat;                                   # cs_pixmat                     is from   src/lib/x-kit/xclient/src/window/cs-pixmat.pkg
#   package dy  =  display;                                     # display                       is from   src/lib/x-kit/xclient/src/wire/display.pkg
#   package fil =  file__premicrothread;                        # file__premicrothread          is from   src/lib/std/src/posix/file--premicrothread.pkg
#   package fti =  font_index;                                  # font_index                    is from   src/lib/x-kit/xclient/src/window/font-index.pkg
#   package r2k =  xevent_router_to_keymap;                     # xevent_router_to_keymap       is from   src/lib/x-kit/xclient/src/window/xevent-router-to-keymap.pkg
#   package mtx =  rw_matrix;                                   # rw_matrix                     is from   src/lib/std/src/rw-matrix.pkg
#   package rop =  ro_pixmap;                                   # ro_pixmap                     is from   src/lib/x-kit/xclient/src/window/ro-pixmap.pkg
#   package rw  =  root_window;                                 # root_window                   is from   src/lib/x-kit/widget/lib/root-window.pkg
#   package rwv =  rw_vector;                                   # rw_vector                     is from   src/lib/std/src/rw-vector.pkg
#   package sep =  client_to_selection;                         # client_to_selection           is from   src/lib/x-kit/xclient/src/window/client-to-selection.pkg
#   package shp =  shade;                                       # shade                         is from   src/lib/x-kit/widget/lib/shade.pkg
#   package sj  =  socket_junk;                                 # socket_junk                   is from   src/lib/internet/socket-junk.pkg
#   package x2s =  xclient_to_sequencer;                        # xclient_to_sequencer          is from   src/lib/x-kit/xclient/src/wire/xclient-to-sequencer.pkg
#   package tr  =  logger;                                      # logger                        is from   src/lib/src/lib/thread-kit/src/lib/logger.pkg
#   package tsr =  thread_scheduler_is_running;                 # thread_scheduler_is_running   is from   src/lib/src/lib/thread-kit/src/core-thread-kit/thread-scheduler-is-running.pkg
#   package u1  =  one_byte_unt;                                # one_byte_unt                  is from   src/lib/std/one-byte-unt.pkg
#   package v1u =  vector_of_one_byte_unts;                     # vector_of_one_byte_unts       is from   src/lib/std/src/vector-of-one-byte-unts.pkg
#   package v2w =  value_to_wire;                               # value_to_wire                 is from   src/lib/x-kit/xclient/src/wire/value-to-wire.pkg
#   package wg  =  widget;                                      # widget                        is from   src/lib/x-kit/widget/old/basic/widget.pkg
#   package wi  =  window;                                      # window                        is from   src/lib/x-kit/xclient/src/window/window.pkg
#   package wme =  window_map_event_sink;                       # window_map_event_sink         is from   src/lib/x-kit/xclient/src/window/window-map-event-sink.pkg
#   package wpp =  client_to_window_watcher;                    # client_to_window_watcher      is from   src/lib/x-kit/xclient/src/window/client-to-window-watcher.pkg
#   package wy  =  widget_style;                                # widget_style                  is from   src/lib/x-kit/widget/lib/widget-style.pkg
#   package xc  =  xclient;                                     # xclient                       is from   src/lib/x-kit/xclient/xclient.pkg
#   package xj  =  xsession_junk;                               # xsession_junk                 is from   src/lib/x-kit/xclient/src/window/xsession-junk.pkg
#   package xtr =  xlogger;                                     # xlogger                       is from   src/lib/x-kit/xclient/src/stuff/xlogger.pkg
    #

    #
    package evt =  gui_event_types;                             # gui_event_types               is from   src/lib/x-kit/widget/gui/gui-event-types.pkg
    package gts =  gui_event_to_string;                         # gui_event_to_string           is from   src/lib/x-kit/widget/gui/gui-event-to-string.pkg
    package gt  =  guiboss_types;                               # guiboss_types                 is from   src/lib/x-kit/widget/gui/guiboss-types.pkg

    package a2r =  windowsystem_to_xevent_router;               # windowsystem_to_xevent_router is from   src/lib/x-kit/xclient/src/window/windowsystem-to-xevent-router.pkg

    package gd  =  gui_displaylist;                             # gui_displaylist               is from   src/lib/x-kit/widget/theme/gui-displaylist.pkg

    package pp  =  standard_prettyprinter;                      # standard_prettyprinter        is from   src/lib/prettyprint/big/src/standard-prettyprinter.pkg
    package lms =  list_mergesort;                              # list_mergesort                is from   src/lib/src/list-mergesort.pkg

    package err =  compiler::error_message;                     # compiler                      is from   src/lib/core/compiler/compiler.pkg
                                                                # error_message                 is from   src/lib/compiler/front/basics/errormsg/error-message.pkg


    package ct  =  cutbuffer_types;                             # cutbuffer_types               is from   src/lib/x-kit/widget/edit/cutbuffer-types.pkg
#   package ct  =  gui_to_object_theme;                         # gui_to_object_theme           is from   src/lib/x-kit/widget/theme/object/gui-to-object-theme.pkg
#   package bt  =  gui_to_sprite_theme;                         # gui_to_sprite_theme           is from   src/lib/x-kit/widget/theme/sprite/gui-to-sprite-theme.pkg
#   package wt  =  widget_theme;                                # widget_theme                  is from   src/lib/x-kit/widget/theme/widget/widget-theme.pkg


    package boi =  spritespace_imp;                             # spritespace_imp               is from   src/lib/x-kit/widget/space/sprite/spritespace-imp.pkg
    package cai =  objectspace_imp;                             # objectspace_imp               is from   src/lib/x-kit/widget/space/object/objectspace-imp.pkg
    package pai =  widgetspace_imp;                             # widgetspace_imp               is from   src/lib/x-kit/widget/space/widget/widgetspace-imp.pkg

    #    
    package gtg =  guiboss_to_guishim;                          # guiboss_to_guishim            is from   src/lib/x-kit/widget/theme/guiboss-to-guishim.pkg

    package b2s =  spritespace_to_sprite;                       # spritespace_to_sprite         is from   src/lib/x-kit/widget/space/sprite/spritespace-to-sprite.pkg
    package c2o =  objectspace_to_object;                       # objectspace_to_object         is from   src/lib/x-kit/widget/space/object/objectspace-to-object.pkg

    package s2b =  sprite_to_spritespace;                       # sprite_to_spritespace         is from   src/lib/x-kit/widget/space/sprite/sprite-to-spritespace.pkg
    package o2c =  object_to_objectspace;                       # object_to_objectspace         is from   src/lib/x-kit/widget/space/object/object-to-objectspace.pkg

    package g2p =  gadget_to_pixmap;                            # gadget_to_pixmap              is from   src/lib/x-kit/widget/theme/gadget-to-pixmap.pkg

    package im  =  int_red_black_map;                           # int_red_black_map             is from   src/lib/src/int-red-black-map.pkg
#   package is  =  int_red_black_set;                           # int_red_black_set             is from   src/lib/src/int-red-black-set.pkg
    package sm  =  string_map;                                  # string_map                    is from   src/lib/src/string-map.pkg
    package idm =  id_map;                                      # id_map                        is from   src/lib/src/id-map.pkg
    package dxy =  digraphxy;                                   # digraphxy                     is from   src/lib/src/digraphxy.pkg

    package sj  =  string_junk;                                 # string_junk                   is from   src/lib/std/src/string-junk.pkg
    package r8  =  rgb8;                                        # rgb8                          is from   src/lib/x-kit/xclient/src/color/rgb8.pkg
    package r64 =  rgb;                                         # rgb                           is from   src/lib/x-kit/xclient/src/color/rgb.pkg
    package g2d =  geometry2d;                                  # geometry2d                    is from   src/lib/std/2d/geometry2d.pkg
    package g2j =  geometry2d_junk;                             # geometry2d_junk               is from   src/lib/std/2d/geometry2d-junk.pkg

    package mt  =  millboss_types;                              # millboss_types                is from   src/lib/x-kit/widget/edit/millboss-types.pkg

    package a2c  = app_to_compileimp;                           # app_to_compileimp             is from   src/lib/x-kit/widget/edit/app-to-compileimp.pkg
    package e2g =  millboss_to_guiboss;                         # millboss_to_guiboss           is from   src/lib/x-kit/widget/edit/millboss-to-guiboss.pkg

    package tbi =  textmill;                                    # textmill                      is from   src/lib/x-kit/widget/edit/textmill.pkg
    package tmt =  textmill_crypts;                             # textmill_crypts               is from   src/lib/x-kit/widget/edit/textmill-crypts.pkg

    package p2l =  textpane_to_screenline;                      # textpane_to_screenline        is from   src/lib/x-kit/widget/edit/textpane-to-screenline.pkg
    package l2p =  screenline_to_textpane;                      # screenline_to_textpane        is from   src/lib/x-kit/widget/edit/screenline-to-textpane.pkg
    #
    package b2p =  millboss_to_pane;                            # millboss_to_pane              is from   src/lib/x-kit/widget/edit/millboss-to-pane.pkg

    package mmo =  millgraph_millout;                           # millgraph_millout             is from   src/lib/x-kit/widget/edit/millgraph-millout.pkg
    package fm  =  fundamental_mode;                            # fundamental_mode              is from   src/lib/x-kit/widget/edit/fundamental-mode.pkg
    package mtp =  modes_to_preload;                            # modes_to_preload              is from   src/lib/x-kit/widget/edit/modes-to-preload.pkg

    tracefile   =  "widget-unit-test.trace.log";

    nb = log::note_on_stderr;                                   # log                           is from   src/lib/std/src/log.pkg

    Dummy = mtp::Dummy;                                         # We force modes-to-preload to load and it forces the stocks modes to preload.

dummy1 = make_textpane::make_pane_guiplan;      # XXX SUCKO FIXME Clumsy way to force this to compile and load. Should think of a better. The problem is that it is never called directly, just backpatches itself into a refcell, so the usual dependency mechanisms do not kick in.

herein

    package millboss_imp
    :       Millboss_Imp                                                                                                # Millboss_Imp          is from   src/lib/x-kit/widget/edit/millboss-imp.api
    {
        Millboss_Option
          #
          =  MICROTHREAD_NAME   String                                                                                  # 
          |  ID                 Id                                                                                      # Stable, unique id for imp.
          ;     

        Millboss_Arg =  List(Millboss_Option);                                                                          # Currently no required component.

        Imports = {                                                                                                     # Ports we use, provided by other imps.
                    millboss_to_guiboss:        e2g::Millboss_To_Guiboss,
                    app_to_compileimp:          a2c::App_To_Compileimp
                  };
                                                                                                                        # 

        Millgraph_Watchers                                                                                              # Type for tracking the set of clients subscribed to a mill for mt::Millgraph updates.
            =                                                                                                           #
            mt::ipm::Map( (mt::Inport, (mt::Outport, mt::Millgraph) -> Void) );                                         # 

        Pane_Info
          =
          { pane_id:                            Id,
            pane_tag:                           Int,                                                                    # We assign each pane a small positive Int tag to be displayed on modeline and used by "C-x o" (other_pane) in   src/lib/x-kit/widget/edit/fundamental-mode.pkg
            mill_id:                            Id,                                                                     # The mill displayed in the pane.
            millboss_to_pane:                   b2p::Millboss_To_Pane                                                   # Our port to the pane.
          };

        Per_Mill_Wakeup_Info                                                                                            # Infrastructure for the wakeup service we provide to mills.
          =
          {
            at_frame_n:         Ref (   Null_Or                                                                         # Call mill.wakeup once, during frame N, and pass wakeup_fn in call. NULL means this wakeup is off.
                                          { at_frame:   Int,
                                            wakeup_fn:  mt::Wakeup_Arg -> Void
                                          }
                                    ),
            every_n_frames:     Ref (   Null_Or                                                                         # Call gadget.wakeup every N frames,       and pass wakeup_fn in call. NULL means this wakeup is off.
                                          { n:          Int,
                                            next:       Ref(Int),
                                            wakeup_fn:  mt::Wakeup_Arg -> Void
                                          }
                                    )
          };
          
        Millboss_State                                                                                                  # 
          =
          { mills_by_name:                      Ref(  sm::Map( mt::Mill_Info ) ),                                       # All currently active mills, by name.
            mills_by_id:                        Ref( idm::Map( mt::Mill_Info ) ),                                       # All currently active mills, by id. Maintained by note_mill_info(). NEVER DELETED AS YET. XXX SUCKO FIXME.
            mills_by_filepath:                  Ref(  sm::Map( mt::Mill_Info ) ),                                       # All currently active mills WHICH ARE OPEN ON A FILE, by filepath. (We expect this to typically be a full pathname like "/home/jayne/foo.txt", so as to help avoid having multiple buffers open on one file.)
            #
            millwatches:                        Ref( mt::mwm::Map (mt::Millwatch )),                                    # Tracks which mill inports are watching which outports, maintained via note_millwatch + drop_millwatch.
            #
            mill_wakeups:                       Ref( idm::Map( Per_Mill_Wakeup_Info )),                                 # Support for wakeme calls to mills.
            current_frame_number:               Ref( Int ),                                                             # "                               ".
            #
            pending_pane_mail:                  Ref( idm::Map( List( Crypt ) ) ),                                       # Messages to panes which have not yet registered with us, indexed by pane_id.  To preserve message order we reverse these lists before delivering them  (although message order should rarely if ever matter).
            panes_by_id:                        Ref( idm::Map( Pane_Info           ) ),
            #
            name:                               Ref( String                   )                                         # Name of millboss for display purposes.
          };

        Me_Slot = Mailslot( { imports:          Imports,
                              me:               Millboss_State,
                              millboss_arg:     Millboss_Arg,
                              run_gun':         Run_Gun,
                              end_gun':         End_Gun
                            }
                          );

        Guiboss_To_Millboss
          =
          { do_one_frame:       Int -> Void                                                                             # Called by guiboss at 50Hz to allow millboss to do periodic stuff, mostly wakeme service for mills.  Int arg is current_frame_number.
          };

        Exports
          =
          { guiboss_to_millboss:                Guiboss_To_Millboss                                                     # Ports we provide for use by other imps.
          };


        Millboss_Egg =  Void -> (Exports,   (Imports, Run_Gun, End_Gun) -> Void);


        Runstate =    {                                                                                                 # These values will be statically globally visible throughout the code body for the imp.
                        id:                     Id,
                        me:                     Millboss_State,                                                         # 
                        millboss_arg:           Millboss_Arg,
                        imports:                Imports,                                                                # Imps to which we send requests.
                        to:                     Replyqueue,                                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                        #                                                                                               #
                        millgraph_outport:      mt::Outport,                                                            # Name of     port on which we stream out millgraphs.
                        millgraph_millout:      mt::Millout,                                                            #             Port on which we stream out millgraphs.
                        millgraph_watchers:     Ref( Millgraph_Watchers ),                                              # Watchers of port on which we stream out millgraphs.
                        #                                                                                               #
                        end_gun':               End_Gun                                                                 # We shut down the microthread when this fires.
                      };

        Millboss_Q    = Mailqueue( Runstate -> Void );



        fun make_millgraph  (r:  Runstate):  mt::Millgraph                                                              # Construct a client-friendly representation of all running mills together with their watcher/watchee relationships.
            =                                                                                                           # Eventually we may want to keep an incrementally updated millgraph, but for now recreating it from scratch as needed is more robust and gives better code modularity.
            {   digraph =  dxy::empty_graph:    dxy::Graph( mt::Millgraph_Node, Void );                                 # Set up to create the digraph of which mill inports are watching which mill outports.
                #
                mill_nodes      =     idm::empty:      idm::Map(dxy::Node(mt::Millgraph_Node));                         # Maps Id        values to digraph::Node values to map mill       values into digraph land.     This map contains one Mill_Info value for each running mill.
                inport_nodes    = mt::ipm::empty:  mt::ipm::Map(dxy::Node(mt::Millgraph_Node));                         # Maps Inport    values to digraph::Node values to map inport     values into digraph land.     This map contains one Millin    value for each input  port on (any) running mill.
                outport_nodes   = mt::opm::empty:  mt::opm::Map(dxy::Node(mt::Millgraph_Node));                         # Maps Outport   values to digraph::Node values to map outport    values into digraph land.     This map contains one Millout   value for each output port on (any) running mill.

                edge_tags       =      sm::empty:       sm::Map(dxy::Tag(Void));                                        # Maps port_type values to digraph::Tag  values to map watcher/ee values into digraph land.     This map contains one Tag for each distinct port_type string in a Millin or Millout (i.e., each type of datastream in the millgraph).

                my (mill_nodes, inport_nodes, outport_nodes, edge_tags, digraph)                                        # Establish digraph nodes for all our Mill_Info, Millin and Millout values, plus the  Mill_Info -> Millout  and  Mill_In -> Mill_Info  edges.
                    =
                    add_nodes
                      (
                        idm::keyvals_list *r.me.mills_by_id,
                        mill_nodes,
                        inport_nodes,
                        outport_nodes,
                        edge_tags,
                        digraph
                      )
                    where
                        fun add_nodes ([], mill_nodes, inport_nodes, outport_nodes, edge_tags, digraph)
                                =>
                                (mill_nodes, inport_nodes, outport_nodes, edge_tags, digraph);

                            add_nodes
                              (
                                (mill_id: Id,   mill_info: mt::Mill_Info)   !   rest,
                                mill_nodes,
                                inport_nodes,
                                outport_nodes,
                                edge_tags,
                                digraph
                              )
                                =>
                                {
                                    my (mill_nodes, millnode)
                                        =
                                        case (idm::get (mill_nodes, mill_id))
                                                    #
                                                    THE millnode => (mill_nodes, millnode);

                                                    NULL  =>
                                                        {   mill_node  =  dxy::make_other_node  (mt::MILL_INFO mill_info);
                                                            #
                                                            mill_nodes =  idm::set (mill_nodes, mill_id, mill_node);

                                                            (mill_nodes, mill_node);
                                                        };
                                                esac;

                                    my (inport_nodes, edge_tags, digraph)
                                        =
                                        add_millins (mt::ipm::keyvals_list mill_info.millins, inport_nodes, edge_tags, digraph)
                                        where
                                            fun add_millins ([], inport_nodes, edge_tags, digraph)
                                                    =>
                                                    (inport_nodes, edge_tags, digraph);

                                                add_millins ((inport: mt::Inport,  millin: mt::Millin)  !  rest,   inport_nodes,  edge_tags,  digraph)
                                                    =>
                                                    {
                                                        my (inport_nodes, inportnode)
                                                            =
                                                            case (mt::ipm::get (inport_nodes, inport))
                                                                        #
                                                                        THE inportnode => (inport_nodes, inportnode);

                                                                        NULL  =>
                                                                            {   inportnode  =  dxy::make_other_node  (mt::MILLIN millin);
                                                                                #
                                                                                inport_nodes =  mt::ipm::set (inport_nodes, inport, inportnode);

                                                                                (inport_nodes, inportnode);
                                                                            };
                                                                    esac;

                                                        my (edge_tags, edgetag)
                                                            =
                                                            case (sm::get (edge_tags, millin.port_type))
                                                                #
                                                                THE edgetag => (edge_tags, edgetag);

                                                                NULL  =>
                                                                    {   edgetag  =  dxy::make_tag ();
                                                                        #
                                                                        edge_tags = sm::set (edge_tags, millin.port_type, edgetag);

                                                                        (edge_tags, edgetag);
                                                                    };
                                                            esac;

                                                        digraph = dxy::put_edge (digraph, (inportnode, edgetag, millnode));

                                                        add_millins (rest, inport_nodes, edge_tags, digraph);
                                                    };
                                            end;
                                        end;

                                    my (outport_nodes, edge_tags, digraph)
                                        =
                                        add_millouts (mt::opm::keyvals_list mill_info.millouts,  outport_nodes,  edge_tags,  digraph)
                                        where
                                            fun add_millouts ([], outport_nodes, edge_tags, digraph)
                                                    =>
                                                    (outport_nodes, edge_tags, digraph);

                                                add_millouts ((outport: mt::Outport,  millout: mt::Millout)  !  rest,  outport_nodes,  edge_tags,   digraph)
                                                    =>
                                                    {
                                                        my (outport_nodes, outportnode)
                                                            =
                                                            case (mt::opm::get (outport_nodes, outport))
                                                                        #
                                                                        THE outportnode => (outport_nodes, outportnode);

                                                                        NULL  =>
                                                                            {   outportnode  =  dxy::make_other_node  (mt::MILLOUT millout);
                                                                                #
                                                                                mt::opm::set (outport_nodes, outport, outportnode);

                                                                                (outport_nodes, outportnode);
                                                                            };
                                                                    esac;

                                                        my (edge_tags, edgetag)
                                                            =
                                                            case (sm::get (edge_tags, millout.port_type))
                                                                #
                                                                THE edgetag => (edge_tags, edgetag);

                                                                NULL  =>
                                                                    {   edgetag  =  dxy::make_tag ();
                                                                        #
                                                                        sm::set (edge_tags, millout.port_type, edgetag);

                                                                        (edge_tags, edgetag);
                                                                    };
                                                            esac;

                                                        digraph = dxy::put_edge (digraph, (millnode, edgetag, outportnode));

                                                        add_millouts (rest, outport_nodes, edge_tags, digraph);
                                                    };
                                            end;
                                        end;

                                    add_nodes (rest, mill_nodes, inport_nodes, outport_nodes, edge_tags, digraph);
                                };
                        end;
                    end;


                digraph                                                                                                 # Establish digraph edges for our  Millout -> Millin  watchee/watcher relationships.
                    =
                    add_edges (mt::mwm::vals_list *r.me.millwatches,  digraph)
                    where
                        fun add_edges ([], digraph)
                                =>
                                digraph;

                            add_edges
                              (
                                millwatch ! rest:       List( mt::Millwatch ),
                                digraph:                dxy::Graph( mt::Millgraph_Node, Void )
                              )
                                =>
                                {   millwatch -> { millin, millout };
                                    #
                                    inport     =  millin.inport;
                                    outport    =  millout.outport;

                                    inportnode =    case (mt::ipm::get (inport_nodes, inport))
                                                                #
                                                                THE inportnode => inportnode;

                                                                NULL  =>    {   msg = "inport not in digraph?! -- millboss_imp::make_millgraph";
                                                                                log::fatal msg;
                                                                                raise exception DIE msg;
                                                                            };
                                                    esac;

                                    outportnode =   case (mt::opm::get (outport_nodes, outport))
                                                                #
                                                                THE outportnode => outportnode;

                                                                NULL  =>    {   msg = "outport not in digraph?! -- millboss_imp::make_millgraph";
                                                                                log::fatal msg;
                                                                                raise exception DIE msg;
                                                                            };
                                                    esac;

                                    port_type =     case (dxy::node_other inportnode)
                                                        #
                                                        THE (mt::MILLIN millin) =>   millin.port_type;

                                                        _   =>  {   msg = "millin node not mt::MILLIN? --    millboss_imp::make_millgraph";
                                                                    log::fatal msg;
                                                                    raise exception DIE msg;
                                                                };
                                                    esac;

                                    edgetag =       case (sm::get (edge_tags, port_type))
                                                        #
                                                        THE edgetag => edgetag;

                                                        NULL  =>    {   msg = "port_type not in edge_tags?!  --    millboss_imp::make_millgraph";
                                                                        log::fatal msg;
                                                                        raise exception DIE msg;
                                                                    };
                                                    esac;

                                    digraph = dxy::put_edge (digraph, (outportnode, edgetag, inportnode));

                                    add_edges (rest, digraph);
                                };
                        end; 
                    end;


                millgraph
                  =
                  { mills_by_id         =>  *r.me.mills_by_id,
                    mills_by_name       =>  *r.me.mills_by_name,
                    mills_by_filepath   =>  *r.me.mills_by_filepath,
                    #
                    digraph,
                    #
                    mill_nodes,
                    inport_nodes,
                    outport_nodes,
                    edge_tags
                  };

                millgraph;
            };

        ##############################################################
        # The result of 'make_millgraph' depends on the values:
        #     *me.mills_by_id
        #     *me.millwatches
        #     *me.mills_by_name
        #     *me.mills_by_filepath
        # so in order to keep millgraph watchers up-to-date,
        # the code in this file NEEDS TO CALL US any after
        # updating any of those values.
        #
        fun maybe_update_millgraph_watchers
              (
                r:                              Runstate
              )
            =
            if (not (mt::ipm::is_empty  *r.millgraph_watchers))
                #
                millgraph =  make_millgraph  r;

                mt::ipm::apply   tell_watcher   *r.millgraph_watchers
                where
                    fun tell_watcher
                          (
                            inport:      mt::Inport,                                                                    # Unique id identifying this watcher.
                            watcher:    (mt::Outport, mt::Millgraph) -> Void                                            # 
                          )
                        =
                        {   outport = r.millgraph_outport;
                            #
                            watcher  (outport, millgraph);

                            counter   =  r.millgraph_millout.counter;                                                   # Count messages sent through port,
                            counter  :=  *counter + 1;                                                                  # for debug/display purposes.
                        };
                end;
            fi;

        fun tell_millgraph_watcher_current_state
              (
                watchfn:                (mt::Outport, mt::Millgraph) -> Void,
                r:                      Runstate
              ) 
            =
            {   outport = r.millgraph_outport;
                #
                millgraph =  make_millgraph r;

                watchfn  (outport, millgraph);
            };

#       fun tell_millgraph_watchers_current_state                                                                       # Commented out because currently never used.
#             (
#               r:                      Runstate
#             )
#           =
#           mt::ipm::apply   tell_watcher   *r.millgraph_watchers
#           where
#               fun tell_watcher
#                     (
#                       inport:          mt::Inport,                                                                    # 
#                       watchfn:        (mt::Outport, mt::Millgraph) -> Void                                            # 
#                     )
#                   =
#                   tell_millgraph_watcher_current_state (watchfn, r);
#           end;

        fun note_mill_info
              (
                runstate:                       Runstate,
                mill_info:                      mt::Mill_Info
              ) 
            =
            {   me = runstate.me;
                #
                me.mills_by_id   := idm::set (*me.mills_by_id,   mill_info.mill_id, mill_info);
                me.mills_by_name :=  sm::set (*me.mills_by_name, mill_info.name,    mill_info);

                case mill_info.filepath
                    #
                    THE filepath
                        =>
                        {   me.mills_by_filepath
                                :=
                                sm::set (*me.mills_by_filepath, filepath,  mill_info);

                            maybe_update_millgraph_watchers  runstate;
                        };
                    NULL => ();
                esac;

                maybe_update_millgraph_watchers  runstate;
            };

        fun run ( millboss_q:                   Millboss_Q,                                                             # 
                  #
                  runstate as
                  {                                                                                                     # These values will be statically globally visible throughout the code body for the imp.
                    id:                         Id,
                    me:                         Millboss_State,                                                         # 
                    millboss_arg:               Millboss_Arg,
                    imports:                    Imports,                                                                # Imps to which we send requests.
                    to:                         Replyqueue,                                                             # The name makes   foo::pass_something(imp) to {. ... }   syntax read well.
                    #                                                                                                   #
                    millgraph_outport:          mt::Outport,                                                            # 
                    millgraph_millout:          mt::Millout,                                                            # 
                    millgraph_watchers:         Ref( Millgraph_Watchers ),                                              # 
                    #                                                                                                   #
                    end_gun':                   End_Gun                                                                 # 
                  }
                )
            =
            {   loop ();
            }
            where


                #
                fun loop ()                                                                                             # Outer loop for the imp.
                    =
                    {   do_one_mailop' to [
                            #
                            end_gun'                        ==>  shut_down_millboss_imp',
                            take_from_mailqueue' millboss_q ==>  do_millboss_plea
                        ];

                        loop ();
                    }   
                    where
                        fun do_millboss_plea  thunk
                            =
                            thunk runstate;
                        #
                        fun shut_down_millboss_imp' ()
                            =
                            {
                                thread_exit { success => TRUE };                                                        # Will not return.      
                            };
                    end;
            end;        



        #
        fun startup   (id: Id,   reply_oneshot:  Oneshot_Maildrop( (Me_Slot, Exports) ))   ()                           # Root fn of imp microthread.  Note currying.
            =
            {   me_slot  =  make_mailslot  ()   :  Me_Slot;
                #
                millouts =  make_millouts (millgraph_outport, *millgraph_millout);

                app_to_mill                                                                                             # Generic interface supported by all mills.  Guiboss pretends to be a kinda-sorta mill itself to allow clients to subscribe to its Millgraph Outport via standard intermill connection protocols.
                    =
                    mt::APP_TO_MILL
                      {
                        id,
                        millins  =>  mt::ipm::empty,
                        millouts,
                        #
                        get_dirty,
                        pass_dirty,

                        get_filepath,
                        set_filepath,
                        pass_filepath,

                        get_name,
                        set_name,
                        pass_name,

                        reload_from_file,
                        save_to_file,

                        get_pane_guiplan,
                        pass_pane_guiplan
                      };

                mill_to_millboss
                    =
                    mt::MILL_TO_MILLBOSS
                      {
                        id,
                        get_textmill,
                        make_textmill,
                        get_or_make_textmill,
                        get_or_make_filebuffer,
                        #
                        get_cutbuffer_contents,
                        set_cutbuffer_contents,
                        #
                        note_pane,
                        drop_pane,
                        mail_pane,
                        #
                        get_panes_by_id,
                        get_mills_by_name,
                        get_mills_by_id,
                        #
                        note_millwatch,
                        drop_millwatch,
                        #
                        wake_me,
                        #
                        app_to_mill                                                                                     # Include App_To_Mill as a sub-interface to allow clients to subscribe to our Millgraph Outport via standard intermill connection protocols.
                      };

                mt::mill_to_millboss__global
                    :=
                    THE mill_to_millboss;

                guiboss_to_millboss
                  =
                  { do_one_frame                                                                                        # Guiboss calls this at 50Hz to help us service mill wakeups.
                  };

                exports     = { guiboss_to_millboss };

                to          =  make_replyqueue();
                #
                put_in_oneshot (reply_oneshot, (me_slot, exports));                                                     # Return value from millboss_egg'().

                (take_from_mailslot  me_slot)                                                                           # Imports from millboss_egg'().
                    ->
                    { me, millboss_arg, imports, run_gun', end_gun' };

                block_until_mailop_fires  run_gun';                                                                     # Wait for the starting gun.

                millgraph_watchers =  REF mt::ipm::empty;

                run ( millboss_q,                                                                                       # Will not return.
                      { id,
                        me,
                        millboss_arg,
                        imports,
                        to,
                        #
                        millgraph_outport,
                        millgraph_millout => *millgraph_millout,                                                        # 
                        millgraph_watchers,
                        #
                        end_gun'
                      }
                );
            }
            where
                millboss_q     =  make_mailqueue (get_current_microthread()):  Millboss_Q;


                millgraph_outport
                  =
                  { mill_id      => id,
                    outport_name => "millgraph"
                  };

                millgraph_millout                                                                                       # First half of a grody little hack to deal with mutual recursion between millgraph__millout and note_millgraph_watcher + drop_millgraph_watcher.
                    =                                                                                                   #
                    REF (                                                                                               #
                        mmo::wrap__millgraph_millout                                                                    # Wrap it so millboss, millgraph-mill &tc don't need to know about port-specific types.
                          (                                                                                             #
                            millgraph_outport,                                                                          #
                            millgraph_millout                                                                           #
                          )                                                                                             #
                        where
                            fun dummy__note_millgraph_watcher                                                           #
                                  (                                                                                     #
                                    watcher:            mt::Inport,                                                     # 
                                    millin:             Null_Or(mt::Millin),                                            # This will be NULL if watcher is not another mill (e.g. a pane).
                                    watchfn:            (mt::Outport, mt::Millgraph) -> Void                            # 
                                  )                                                                                     #
                                =                                                                                       #
                                ();                                                                                     # Just has to be type correct.

                            fun dummy__drop_millgraph_watcher (watcher:  mt::Inport)                                    # 
                                =                                                                                       #
                                ();                                                                                     # Just has to be type correct.

                            millgraph_millout                                                                           #
                                =                                                                                       #
                                { note_watcher =>  dummy__note_millgraph_watcher,                                       #
                                  drop_watcher =>  dummy__drop_millgraph_watcher                                        #
                                };                                                                                      #
                        end     
                    );

                fun note_textmill_statechange                                                                           # Track textmill name changes.
                      (
                        outport:        mt::Outport,
                        statechange:    mt::Textmill_Statechange
                      )
                    =
                    case statechange
                        #
                        mt::NAME_CHANGED { was: String, now: String }
                            =>
                            put_in_mailqueue  (millboss_q,
                                #
                                \\ (runstate as { me, ... }: Runstate)
                                    =
                                    case (idm::get  (*me.mills_by_id,  outport.mill_id))
                                        #
                                        THE mill_info
                                            =>
                                            {   mill_info                                                               # Remember new name of mill.  This is one of those places where functional record update support in the compiler would be nice.
                                                  =
                                                  { name                =>  now,                                        # The updated field.
                                                    freshness           =>  id_to_int (issue_unique_id()),              # Might as well update freshness too.
                                                    #
                                                    mill_id             =>  mill_info.mill_id,                          # The unchanged fields.
                                                    app_to_mill         =>  mill_info.app_to_mill,                      #
                                                    pane_to_mill        =>  mill_info.pane_to_mill,                     #
                                                    filepath            =>  mill_info.filepath,                         #
                                                    millins             =>  mill_info.millins,                          #
                                                    millouts            =>  mill_info.millouts,                         #
                                                    millboss_to_mill    =>  mill_info.millboss_to_mill                  #
                                                  };

                                                me.mills_by_name :=  sm::drop (*me.mills_by_name, was);                 # Forget   old name of mill.
                                                note_mill_info (runstate, mill_info);                                   # Remember mill under its new name.
                                            };
                                        #
                                        NULL =>
                                            {   msg =  sprintf "outport.id (%d) not in *me.mills_by_id!"
                                                               (id_to_int outport.mill_id);
                                                log::fatal msg;
                                                raise exception DIE msg;
                                            };
                                    esac
                            );  


                        mt::TEXTSTATE_CHANGED _ =>  ();                                                                 # We list the rest explicitly here so as to draw a compile error if a new one gets added without us being updated.
                        mt::UNDO              _ =>  ();
                        mt::FILEPATH_CHANGED  _ =>  ();
                        mt::READONLY_CHANGED  _ =>  ();
                        mt::DIRTY_CHANGED     _ =>  ();
                    esac;

                #################################################################################
                # mill_to_millboss interface fns::
                #
                #

                fun find_first_unused_pane_tag                                                                          # 
                      (                                                                                                 # 
                        me:             Millboss_State
                      ):                Int
                    =
                    {   panes = idm::vals_list  *me.panes_by_id;
                        panes = lms::sort_list gt panes
                                    where
                                        fun gt ( pane1: Pane_Info,
                                                 pane2: Pane_Info
                                               )
                                            =
                                            pane1.pane_tag > pane2.pane_tag;
                                    end;
                        #
                        try (panes, 1)                                                                                  # All pane tags should be greater than zero.
                            where
                                fun try ((pane: Pane_Info) ! rest,  n)                                                  # Search sequentially through sorted list for first unused pane tag value.
                                        =>
                                        if (pane.pane_tag > n)   n;
                                        else                     try (rest, n+1);
                                        fi;

                                    try ([], n) => n;
                                end;
                            end;
                    };  

                fun note_pane                                                                                           # Used to inform us of newly created panes, and also to update us when the mill associated with a pane changes.
                      {                                                                                                 # (Currently the only time we change the mill associated with a pane is in find_file -- fundamental-mode.pkg's switch_to_mill() just creates a new pane.)
                        millboss_to_pane:                       b2p::Millboss_To_Pane,
                        mill_id:                                Id
                      }
                    =
                    put_in_mailqueue  (millboss_q,
                        #
                        \\ (r as { id, me, ... }: Runstate)
                            =
                            {   pane_id  =  millboss_to_pane.pane_id;
                                #
                                pane_tag =  case (idm::get (*me.panes_by_id, pane_id))                                  # We assign each pane the smallest unused positive tag, to be displayed on modeline and used by "C-x o" (other_pane) in   src/lib/x-kit/widget/edit/fundamental-mode.pkg
                                                #
                                                THE pane_info
                                                    =>
                                                    pane_info.pane_tag;                                                 # We already knew about pane, so retain its existing pane_tag.

                                                NULL =>
                                                    {   pane_tag = find_first_unused_pane_tag  me;                      # This is a new pane.
                                                        millboss_to_pane.note_tag    pane_tag;                          # Tell the pane what tag we assigned it.
                                                        pane_tag;
                                                    };
                                            esac;

                                me.panes_by_id                                                                          # Remember what we've learned/decided about this pane.
                                    :=
                                    idm::set  (*me.panes_by_id,  pane_id,  pane_info)
                                        where
                                            pane_info = { pane_id,
                                                          pane_tag,
                                                          mill_id,
                                                          millboss_to_pane
                                                        };
                                        end;


                                case (idm::get (*me.pending_pane_mail, pane_id))                                        # Deliver any pending mail for this pane.
                                    #
                                    THE (pending_pane_mail:  List( Crypt ))
                                        =>
                                        {   apply   do_crypt   (reverse pending_pane_mail)                              # We reverse to restore original message ordering.
                                                    #
                                                    where
                                                        fun do_crypt (crypt: Crypt)
                                                            =
                                                            millboss_to_pane.note_crypt  crypt;
                                                    end;

                                            me.pending_pane_mail                                                        # Forget about delivered mail, so we don't deliver it again.
                                                :=
                                                idm::drop  (*me.pending_pane_mail,   pane_id);
                                        };
                                    NULL => ();
                                esac;

                                case (idm::get (*me.mills_by_id, mill_id))                                              # Freshen mill.
                                    #
                                    THE mill_info
                                        =>
                                        {   freshness =  id_to_int (issue_unique_id());
                                            #
                                            mill_info =   { freshness,                                                  # Updated field.
                                                            #
                                                            mill_id             =>  mill_info.mill_id,                  # Unchanged fields.  Oh, for functional record updates! :-)
                                                            app_to_mill         =>  mill_info.app_to_mill,              #
                                                            pane_to_mill        =>  mill_info.pane_to_mill,             #       
                                                            name                =>  mill_info.name,                     #
                                                            filepath            =>  mill_info.filepath,                 #
                                                            millins             =>  mill_info.millins,                  #
                                                            millouts            =>  mill_info.millouts,                 #
                                                            millboss_to_mill    =>  mill_info.millboss_to_mill          #
                                                          };

                                            note_mill_info (r, mill_info);
                                        };

                                    NULL => ();                                                                         # Mill has not registered yet.
                                esac;
                            }
                    );
                fun mail_pane                                                                                           # Send something to a pane. If the pane is not yet registered with millboss, the crypt will be queued up and delivered when the pane registers. Used for linking up screenline.pkg instances to textpane.pkg instances at startup (etc).
                      (                                                                                                 # Using a Crypt here makes the mechanism general at a small cost in typesafety.  In particular, it buys us valuable modularity by keeping millboss from needing to know the types of the interfaces between textpane and screenline (etc).
                        pane_id:        Id,                                                                             #
                        crypt:          Crypt
                      )
                    =
                    put_in_mailqueue  (millboss_q,
                        #
                        \\ (r as { id, me, ... }: Runstate)
                            =
                            case (idm::get (*me.panes_by_id, pane_id))                                                  # Has the given pane registered with us yet?
                                #
                                THE pane_info                                                                           # Yes,
                                    =>                                                                                  # so
                                    pane_info.millboss_to_pane.note_crypt  crypt;                                       # deliver the mail immediately.

                                NULL =>                                                                                 # No, so queue up the mail for later delivery.
                                    #
                                    case (idm::get (*me.pending_pane_mail, pane_id))                                    # Is there already pending mail for this pane?
                                        #
                                        THE pending_pane_mail                                                           # Yes,
                                            =>                                                                          # so
                                            me.pending_pane_mail                                                        # prepend
                                                :=                                                                      # new mail
                                                idm::set ( *me.pending_pane_mail,                                       # to
                                                           pane_id,                                                     # existing
                                                           crypt ! pending_pane_mail                                    # queued-mail
                                                         );                                                             # list.

                                        NULL =>                                                                         # No, so
                                            me.pending_pane_mail                                                        # start a new
                                                :=                                                                      # list of
                                                idm::set (*me.pending_pane_mail,  pane_id,  [ crypt ]);                 # pending mail for that pane.
                                    esac;
                            esac
                    );
                fun drop_pane
                      {
                        pane_id:                Id
                      }
                    =
                    put_in_mailqueue  (millboss_q,
                        #
                        \\ (r as { id, me, ... }: Runstate)
                            =
                            me.panes_by_id
                                :=
                                idm::drop  (*me.panes_by_id,   pane_id)
                    );
                fun get_panes_by_id ()
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( idm::Map(mt::Pane_Info) );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ (r as { id, me, ... }: Runstate)
                                =
                                {   result = idm::map  do_pane  *me.panes_by_id
                                                where
                                                    fun do_pane ({ pane_id, pane_tag, mill_id, ... }: Pane_Info)
                                                        =
                                                        { pane_id, pane_tag, mill_id };
                                                end;    

                                    put_in_oneshot (reply_oneshot, result);
                                }
                        );
                        get_from_oneshot  reply_oneshot;
                    };
                fun get_mills_by_name ()
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( sm::Map(mt::Mill_Info) );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ (r as { id, me, ... }: Runstate)
                                =
                                {   result = *me.mills_by_name;
                                    #
                                    put_in_oneshot (reply_oneshot, result);
                                }
                        );
                        get_from_oneshot  reply_oneshot;
                    };
                fun get_mills_by_id ()
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( idm::Map(mt::Mill_Info) );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ (r as { id, me, ... }: Runstate)
                                =
                                {   result = *me.mills_by_id;
                                    #
                                    put_in_oneshot (reply_oneshot, result);
                                }
                        );
                        get_from_oneshot  reply_oneshot;
                    };

                fun note_millwatch (millwatch:  mt::Millwatch)                                                          # Remember new instance of inport on one mill watching outport on another mill.
                    =
                    {   put_in_mailqueue  (millboss_q,
                            #
                            \\ (r as { id, me, ... }: Runstate)
                                =
                                {   key = { inport  =>  millwatch.millin.inport,
                                            outport =>  millwatch.millout.outport
                                          };

                                    me.millwatches
                                        :=
                                        mt::mwm::set (*me.millwatches, key, millwatch);

                                    maybe_update_millgraph_watchers r;
                                }
                        );
                    };
                fun drop_millwatch (key:  mt::millwatch_key::Key)                                                       # Opposite of above: Forget that input port was watching output port.
                    =
                    {   put_in_mailqueue  (millboss_q,
                            #
                            \\ (r as { id, me, ... }: Runstate)
                                =
                                {   me.millwatches
                                        :=
                                        mt::mwm::drop (*me.millwatches, key);

                                    maybe_update_millgraph_watchers r;
                                }
                        );
                    };

                fun wake_me                                                                                             # Used to schedule millboss_to_mill.wakeup calls.
                      {
                        id:             Id,
                        options:        List(mt::Wake_Me_Option)
                      }
                    =
                    {   put_in_mailqueue  (millboss_q,
                            #
                            \\ (r as { me, ... }: Runstate)
                                =
                                {   i = case (idm::get (*me.mill_wakeups, id))
                                            #
                                            THE per_mill_wakeup_info => per_mill_wakeup_info;
                                            #
                                            NULL                     => { at_frame_n     => REF NULL,
                                                                          every_n_frames => REF NULL
                                                                        };
                                        esac;

                                    apply  do_option  options
                                    where
                                        fun do_option (option: gt::Wake_Me_Option)
                                            =
                                            case option
                                                #
                                                gt::AT_FRAME_N      NULL                        =>  i.at_frame_n        :=  NULL;
                                                gt::EVERY_N_FRAMES  NULL                        =>  i.every_n_frames    :=  NULL;
                                                #
                                                gt::AT_FRAME_N     (THE (at_frame, wakeup_fn))  =>  i.at_frame_n        :=  THE { at_frame, wakeup_fn };
                                                gt::EVERY_N_FRAMES (THE (n,        wakeup_fn))  =>  i.every_n_frames    :=  THE { n,        wakeup_fn, next => REF ( *me.current_frame_number + n) };
                                            esac;
                                    end;

                                    case  (*i.at_frame_n,  *i.every_n_frames)
                                        #
                                        (NULL, NULL )   =>      me.mill_wakeups := idm::drop (*me.mill_wakeups, id   );
                                        _               =>      me.mill_wakeups := idm::set  (*me.mill_wakeups, id, i);
                                    esac;                                       
                                }
                        );
                    };



                stipulate
                    cutbuffer_contents =  REF (ct::PARTLINE "");
                herein
                    fun get_cutbuffer_contents ()
                        =
                        *cutbuffer_contents;                                                                            # We do this in caller's thread (not millboss thread) for speed and to reduce risk of deadlock.

                    fun set_cutbuffer_contents (new_contents:   ct::Cutbuffer_Contents)
                        =
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                cutbuffer_contents :=  new_contents                                                     # We do this in the millboss thread to ensure cutbuffer_contents value remains stable over any millboss_imp call, just as good practice.
                        );
                end;

                fun uniquify_name                                                                                       # Convert "foo" into "foo<1>" or "foo<2>" or such in order to avoid conflicting with any existing name.
                      (
                        me:             Millboss_State,
                        name:           String
                      )
                    :                   String
                    =
                    case (sm::get (*me.mills_by_name, name))
                        #
                        NULL => name;                                                                                   # Name is already unique.

                        THE _ =>
                            uniquify_name' 0
                            where
                                fun uniquify_name' (i: Int)
                                    =
                                    {   name' = sprintf "%s<%d>" name i;
                                        #
                                        case (sm::get (*me.mills_by_name, name'))
                                            #
                                            NULL  =>  name';                                                            # Modified name is now unique.
                                            THE _ =>  uniquify_name' (i + 1);                                           # Modified name already exists, try one number higher.
                                        esac;
                                    };
                            end;
                    esac;


                fun get_textmill   (name: String)
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Null_Or( mt::Textpane_To_Textmill ) );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                case (tmt::get__null_or_textpane_to_textmill__from__null_or_textmill_info
                                         (sm::get (*me.mills_by_name, name))
                                     )
                                    #
                                    THE textpane_to_textmill =>    put_in_oneshot (reply_oneshot, THE textpane_to_textmill);
                                    NULL                     =>    put_in_oneshot (reply_oneshot, NULL);
                                esac
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                fun make_textmill                                                                                       # Create a buffer, If an existing buffer has given name, modify the new buffer's name to make it unique.
                      (
                        textmill_arg
                        as
                        { name:                 String,
                          textmill_options:     List( mt::Textmill_Option )
                        }:                      mt::Textmill_Arg
                      )
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Textpane_To_Textmill );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ (r as { id, me, ... }: Runstate)
                                =
                                {   name =  uniquify_name (me, name);                                                   # Convert "foo" into "foo<1>" or "foo<2>" or such in order to avoid conflicting with any existing name.
                                    #
                                    egg =  tbi::make_textmill_egg  textmill_arg;
                                    #
                                    (egg ())
                                        ->
                                        ( textmill_exports:     tbi::Exports,
                                          egg':                (tbi::Imports, Run_Gun, End_Gun) -> Void
                                        );

                                    textmill_imports
                                      =
                                      { };

                                    (make_run_gun ()) ->   { run_gun', fire_run_gun };
                                    (make_end_gun ()) ->   { end_gun', fire_end_gun };

                                    egg' (textmill_imports, run_gun', end_gun');

                                    fire_run_gun ();

                                    textmill_exports
                                      ->
                                      { textpane_to_textmill  as  mt::TEXTPANE_TO_TEXTMILL  tb,
                                        millboss_to_mill
                                      };

                                    tb.app_to_mill   ->  mt::APP_TO_MILL am;

                                    millins   =   am.millins ;
                                    millouts  =   am.millouts;

                                    mill_info =   { name,
                                                    freshness    =>  id_to_int (issue_unique_id()),
                                                    mill_id      =>  tb.id,
                                                    filepath     =>  NULL,
                                                    app_to_mill  =>  tb.app_to_mill,
                                                    pane_to_mill =>  tmt::encrypt__textpane_to_textmill  textpane_to_textmill,
                                                    millins,
                                                    millouts,
                                                    millboss_to_mill
                                                  };

                                    note_mill_info (r, mill_info);

                                    watcher = { mill_id => id, inport_name => "" }:  mt::Inport;

                                    tb.note__textmill_statechange__watcher (watcher, NULL, note_textmill_statechange);  # We subscribe just to track textmill name changes.

                                    put_in_oneshot (reply_oneshot, textpane_to_textmill);
                            }
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                fun get_or_make_textmill                                                                                # Find a buffer by name; if no such buffer exists, create one.  Buffer may or may not have an associated file.
                      (
                        textmill_arg
                        as
                        { name:                 String,
                          textmill_options:     List( mt::Textmill_Option )
                        }:                      mt::Textmill_Arg
                      )
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Textpane_To_Textmill );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ (r as { id, me, ... }: Runstate)
                                =
                                case (tmt::get__null_or_textpane_to_textmill__from__null_or_textmill_info
                                         (sm::get (*me.mills_by_name, name))
                                     )
                                    #
                                    THE textpane_to_textmill
                                        => 
                                        put_in_oneshot (reply_oneshot, textpane_to_textmill);
                                    #
                                    NULL =>
                                        {   egg =  tbi::make_textmill_egg  textmill_arg;
                                            #
                                            (egg ())
                                                ->
                                                ( textmill_exports:     tbi::Exports,
                                                  egg':                (tbi::Imports, Run_Gun, End_Gun) -> Void
                                                );

                                            textmill_imports
                                              =
                                              { };

                                            (make_run_gun ()) ->   { run_gun', fire_run_gun };
                                            (make_end_gun ()) ->   { end_gun', fire_end_gun };

                                            egg' (textmill_imports, run_gun', end_gun');

                                            fire_run_gun ();

                                            textmill_exports
                                                ->
                                                { textpane_to_textmill as mt::TEXTPANE_TO_TEXTMILL tb,
                                                  millboss_to_mill
                                                };

                                            tb.app_to_mill   ->  mt::APP_TO_MILL am;

                                            millins   =   am.millins ;
                                            millouts  =   am.millouts;  

                                            mill_info = { name,
                                                          freshness    =>  id_to_int (issue_unique_id()),
                                                          mill_id      =>  tb.id,
                                                          filepath     =>  NULL,
                                                          app_to_mill  =>  tb.app_to_mill,
                                                          pane_to_mill =>  tmt::encrypt__textpane_to_textmill  textpane_to_textmill,
                                                          millins,
                                                          millouts,
                                                          millboss_to_mill
                                                        };

                                            note_mill_info (r, mill_info);

                                            watcher = { mill_id => id, inport_name => "" }:  mt::Inport;

                                            tb.note__textmill_statechange__watcher                                      # We subscribe just to track textmill name changes.
                                              (
                                                watcher,
                                                NULL,
                                                note_textmill_statechange
                                              );

                                            put_in_oneshot (reply_oneshot, textpane_to_textmill);
                                        };
                                esac
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                fun get_or_make_filebuffer                                                                              # Find buffer open on given file.  If no such buffer exists, create one.  NB: We do our best to avoid having more than one buffer open on a given file.  (Multiple textpanes may be open on one buffer; that is a separate issue.)
                      (
                        textmill_arg
                        as
                        { name:                 String,
                          textmill_options:     List( mt::Textmill_Option )
                        }:                      mt::Textmill_Arg
                      )
                      ( 
                        filepath:               String
                      )
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( mt::Textpane_To_Textmill );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ (r as { id, me, ... }: Runstate)
                                =
                                {   name =  if (name == "")     sj::basename filepath;
                                            else                name;
                                            fi;
                                    name = uniquify_name (me, name);
                                    #
                                    textmill_arg     =  { name, textmill_options };

                                    egg =  tbi::make_textmill_egg  textmill_arg;
                                    #
                                    (egg ())
                                        ->
                                        ( textmill_exports:     tbi::Exports,
                                          egg':                (tbi::Imports, Run_Gun, End_Gun) -> Void
                                        );

                                    textmill_imports
                                      =
                                      { };

                                    (make_run_gun ()) ->   { run_gun', fire_run_gun };
                                    (make_end_gun ()) ->   { end_gun', fire_end_gun };

                                    egg' (textmill_imports, run_gun', end_gun');

                                    fire_run_gun ();

                                    textmill_exports ->   { textpane_to_textmill  as  mt::TEXTPANE_TO_TEXTMILL tb,
                                                            millboss_to_mill
                                                          };

                                    tb.app_to_mill   ->  mt::APP_TO_MILL am;

                                    millins   =   am.millins ;
                                    millouts  =   am.millouts;  

                                    mill_info =   { name,
                                                    freshness    =>  id_to_int (issue_unique_id()),
                                                    mill_id      =>  tb.id,
                                                    filepath     =>  THE filepath,
                                                    app_to_mill  =>  tb.app_to_mill,
                                                    pane_to_mill =>  tmt::encrypt__textpane_to_textmill  textpane_to_textmill,
                                                    millins,
                                                    millouts,
                                                    millboss_to_mill
                                                  };

                                    note_mill_info (r, mill_info);

                                    watcher = { mill_id => id, inport_name => "" }:  mt::Inport;

                                    tb.note__textmill_statechange__watcher                                              # We subscribe just to track textmill name changes.
                                      (
                                        watcher,
                                        NULL,
                                        note_textmill_statechange
                                      );

                                    am.set_filepath (THE filepath);

                                    am.reload_from_file ();

                                    put_in_oneshot (reply_oneshot, textpane_to_textmill);
                                }
                        );

                        get_from_oneshot  reply_oneshot;
                    };

                #################################################################################
                # Guiboss_To_Millboss interface fns::
                #
                #
                fun do_one_frame (frame_number: Int)                                                            # Called by guiboss at 50Hz, helps us service mill wakeups.
                    =
                    put_in_mailqueue  (millboss_q,
                        #
                        \\ ({ id, me, ... }: Runstate)
                            =
                            {   wakeups =  idm::keyvals_list  *me.mill_wakeups;
                                #
                                apply do_wakeup wakeups
                                    where
                                        fun find_mill_info (id: Id)
                                            =
                                            case  (idm::get  (*me.mills_by_id,  id))
                                                #
                                                THE mill_info => mill_info;

                                                NULL => {   msg = sprintf "wakeup mill %d not found in mills_by_id  --millboss_imp::do_one_frame"  (id_to_int id);
                                                            log::fatal msg;
                                                            raise exception DIE msg;
                                                        }; 
                                            esac;
        
                                        fun do_wakeup
                                              (
                                                id:     Id,
                                                wu:     Per_Mill_Wakeup_Info
                                              )
                                            =
                                            {   wu ->   {   at_frame_n:         Ref (   Null_Or                                                                         # Call mill.wakeup once, during frame N, and pass wakeup_fn in call. NULL means this wakeup is off.
                                                                                          { at_frame:   Int,
                                                                                            wakeup_fn:  mt::Wakeup_Arg -> Void
                                                                                          }
                                                                                    ),
                                                            every_n_frames:     Ref (   Null_Or                                                                         # Call gadget.wakeup every N frames,       and pass wakeup_fn in call. NULL means this wakeup is off.
                                                                                          { n:          Int,
                                                                                            next:       Ref(Int),
                                                                                            wakeup_fn:  mt::Wakeup_Arg -> Void
                                                                                          }
                                                                                    )
                                                        };

                                                case *at_frame_n
                                                    #
                                                    THE   { at_frame:   Int,
                                                            wakeup_fn:  mt::Wakeup_Arg -> Void
                                                          }
                                                        =>
                                                        if (frame_number == at_frame)
                                                            #   
                                                            mill_info = find_mill_info id;

                                                            mill_info.millboss_to_mill.wakeup { wakeup_arg => { frame_number }, wakeup_fn };

                                                        elif (frame_number  > at_frame)
                                                            #
                                                            case *every_n_frames
                                                                #
                                                                THE _ =>   at_frame_n     := NULL;                                                                      # We've done the call, null out the request for it.
                                                                NULL  =>   me.mills_by_id := idm::drop (*me.mills_by_id, id);                                           # No longer any wakeup work scheduled for this mill, so just drop its record.
                                                            esac;
                                                        fi;

                                                    NULL => ();
                                                esac;

                                                case *every_n_frames
                                                    #
                                                    THE   { n:          Int,
                                                            next:       Ref(Int),
                                                            wakeup_fn:  mt::Wakeup_Arg -> Void
                                                          }
                                                        =>
                                                        if  (frame_number >= *next)
                                                            #
                                                            mill_info = find_mill_info id;
                                                            #
                                                            mill_info.millboss_to_mill.wakeup { wakeup_arg => { frame_number }, wakeup_fn };

                                                            next := frame_number + n;
                                                        fi;

                                                    NULL => ();
                                                esac;
                                            };
                                    end;
                            }
                    );

                    
                #################################################################################
                # App_To_Mill interface fns::
                #
                #
                fun get_pane_guiplan ():        gt::Gp_Widget_Type                                                              # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( gt::Gp_Widget_Type );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                {
#                                   filepath      =  *me.filepath;
#                                   textpane_hint =  *me.textpane_hint;
#                                   #
#                                   gp_widget = *make_pane_guiplan__hack { textpane_to_textmill, filepath, textpane_hint };

# XXX BUGGO FIXME TBD

                                    gp_widget = gt::NULL_WIDGET;

                                    put_in_oneshot (reply_oneshot, gp_widget);
                                }
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_pane_guiplan (replyqueue: Replyqueue)  (reply_handler: gt::Gp_Widget_Type -> Void):    Void    # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( gt::Gp_Widget_Type );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                {
#                                   filepath      =  *me.filepath;
#                                   textpane_hint =  *me.textpane_hint;
#                                   #
#                                   gp_widget = *make_pane_guiplan__hack { textpane_to_textmill, filepath, textpane_hint };

# XXX BUGGO FIXME TBD

                                    gp_widget = gt::NULL_WIDGET;

                                    put_in_oneshot (reply_oneshot, gp_widget);
                                }
                        );

                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };
                    #

                fun get_dirty ()                                                                                        # PUBLIC.  We don't actually support a 'dirty' flag on millboss, so this is always FALSE.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Bool );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, FALSE)
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_dirty  (replyqueue: Replyqueue)  (reply_handler: Bool -> Void)                                 # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Bool );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, FALSE)
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };

                fun set_filepath (filepath: Null_Or( String ))                                                          # PUBLIC.  We don't support a filepath on millboss, so this is a no-op.
                    =
                    {   put_in_mailqueue  (millboss_q,
                            #
                            \\ (runstate as { id, me, millgraph_watchers, ... }: Runstate)
                                =
                                {
                                }
                        );

                    };
                    #
                fun get_filepath ()                                                                                     # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Null_Or( String ) );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, millgraph_watchers, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, NULL)                                                    # We don't support a filepath on millboss, so this is always NULL.
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_filepath (replyqueue: Replyqueue)  (reply_handler: Null_Or( String ) -> Void)                  # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( Null_Or( String ) );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, NULL)                                                    # We don't support a filepath on millboss, so this is always NULL.
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };

                fun set_name (name: String)                                                                             # PUBLIC.
                    =
                    {   put_in_mailqueue  (millboss_q,
                            #
                            \\ (runstate as { id, me, millgraph_watchers, ... }: Runstate)
                                =
                                {
                                    me.name := name;
                                }
                        );
                    };
                    #
                fun get_name ()                                                                                         # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( String );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.name)
                        );

                        get_from_oneshot  reply_oneshot;
                    };
                    #
                fun pass_name  (replyqueue: Replyqueue)  (reply_handler: String -> Void)                                # PUBLIC.
                    =
                    {   reply_oneshot =  make_oneshot_maildrop():  Oneshot_Maildrop( String );
                        #
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                put_in_oneshot (reply_oneshot, *me.name)
                        );
 
                        put_in_replyqueue (replyqueue, (get_from_oneshot' reply_oneshot) ==> reply_handler);
                    };

                fun note_millgraph_watcher                                                                              # PUBLIC.
                      (                                                                                                 #
                        watcher:                mt::Inport,                                                             # 
                        millin:                 Null_Or(mt::Millin),                                                    # This will be NULL if watcher is not another mill (e.g. a pane).
                        watchfn:                (mt::Outport, mt::Millgraph) -> Void                                    # 
                      ) 
                    =
                    {   put_in_mailqueue  (millboss_q,
                            #
                            \\ (runstate as { id, me, millgraph_watchers, ... }: Runstate)
                                =
                                {   millgraph_watchers
                                        :=
                                        mt::ipm::set ( *millgraph_watchers,
                                                        watcher,
                                                        (watcher, watchfn)
                                                     );

                                    tell_millgraph_watcher_current_state (watchfn, runstate);                           # Start out new watcher with current state.
                                }
                        );
                    };
                    #
                fun drop_millgraph_watcher (watcher:  mt::Inport)                                                       # PUBLIC.
                    =
                    {   
                        put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, millgraph_watchers, ... }: Runstate)
                                =
                                millgraph_watchers
                                    :=
                                    mt::ipm::drop (*millgraph_watchers, watcher)
                        );
                    };

                millgraph_millout                                                                                       # Second half of grody little hack to deal with mutual recursion between millgraph__millout and note_millgraph_watcher + drop_millgraph_watcher.
                    :=                                                                                                  #
                    mmo::wrap__millgraph_millout                                                                        # Wrap it so millboss, millgraph-mill &tc don't need to know about port-specific types.
                      (                                                                                                 #
                        millgraph_outport,                                                                              #
                        #                                                                                               #
                        { note_watcher =>  note_millgraph_watcher,                                                      #
                          drop_watcher =>  drop_millgraph_watcher                                                       #
                        }                                                                                               #
                      );                                                                                                #


                fun make_millouts                                                                                       # Construct a description of all our outports, for client use.
                      (                                                                                                 #
                        millgraph_outport:      mt::Outport,                                                            #
                        millgraph_millout:      mt::Millout                                                             #
                      )                                                                                                 #
                    :                           mt::opm::Map(mt::Millout)                                               #
                    =                                                                                                   #
                    {   millouts =  mt::opm::empty;                                                                     # Start with an empty outport map.
                        #                                                                                               #
                        millouts =  mt::opm::set (millouts, millgraph_outport, millgraph_millout);                      # Add our millgraph outport.

                        millouts;                                                                                       # Return map defining all our our outports.
                    };

                fun reload_from_file  ()                                                                                # PUBLIC.  We don't support a filepath or state save/restore so this is a no-op.
                    =
                    {   put_in_mailqueue  (millboss_q,
                            #
                            \\ ({ id, me, ... }: Runstate)
                                =
                                ()
                        );
                    };
                    #
                fun save_to_file  ()                                                                                    # PUBLIC.  We don't support a filepath or state save/restore so this is a no-op.
                    =
                    {   put_in_mailqueue  (millboss_q,
                            #
                            \\ (runstate as  { id, me, millgraph_watchers, ... }: Runstate)
                                =
                                ()
                        );
                    };
            end;


        #
        fun process_options (options: List(Millboss_Option), { name, id })
            =
            {   my_name                 =  REF name;
                my_id                   =  REF id;
                #
                apply  do_option  options
                where
                    fun do_option (MICROTHREAD_NAME     n) =>   my_name                 :=  n;
                        do_option (ID                   i) =>   my_id                   :=  i;
                    end;
                end;

                { name                  =>  *my_name,
                  id                    =>   *my_id
                };
            };


        ##########################################################################################
        # PUBLIC.
        #
        fun make_millboss_egg                                                                                           # PUBLIC. PHASE 1: Construct our state and initialize from 'options'.
              (millboss_arg:            Millboss_Arg)                                                                   # Called (only) by startup()    in   src/lib/x-kit/widget/gui/guiboss-imp.pkg
            =
            {   millboss_arg ->  (millboss_options);                                                                    # Currently no guiboss_needs component, so this is a no-op.
                #
                (process_options
                  ( millboss_options,
                    { name              => "millboss",
                      id                =>  id_zero
                    }
                ) )
                    ->
                    { name,
                      id
                    };
        
                my (id, millboss_options)
                    =
                    if (id_to_int(id) == 0)
                        id = issue_unique_id();                                                                         # Allocate unique imp id.
                        (id, ID id ! millboss_options);                                                                 # Make our id stable across stop/restart cycles.
                    else
                        (id, millboss_options);
                    fi;

                millboss_arg = (millboss_options);                                                                      # Currently no guiboss_needs component, so this is a no-op.

                me =  { mills_by_name           =>  REF  sm::empty,                                                     # All currently active mills, by name.
                        mills_by_id             =>  REF idm::empty,                                                     # All currently active mills, by id.
                        mills_by_filepath       =>  REF  sm::empty,                                                     # All currently active mills WHICH ARE OPEN ON A FILE, by filename.
                        #
                        millwatches             =>  REF  mt::mwm::empty,                                                # Everything we know about mill inports watching mill outports.
                        #
                        mill_wakeups            =>  REF idm::empty,                                                     # Mills which want wakeme calls.
                        current_frame_number    =>  REF 0,
                        #
                        pending_pane_mail       =>  REF idm::empty,                                                     # Messages to panes which have not yet registered with us, indexed by pane_id.  To preserve message order we reverse these lists before delivering them  (although message order should rarely if ever matter).
                        panes_by_id             =>  REF idm::empty,                                                     # All currently active panes, by id.
                        #
                        name                    =>  REF "millboss"                                                      # Name of millboss-imp for display purposes.
                      };

                \\ () = {   reply_oneshot = make_oneshot_maildrop():  Oneshot_Maildrop( (Me_Slot, Exports) );           # PUBLIC. PHASE 2: Start our microthread and return our Exports to caller.
                            #
                            xlogger::make_thread  name  (startup  (id, reply_oneshot));                                 # Note that startup() is curried.

                            (get_from_oneshot  reply_oneshot) -> (me_slot, exports);

                            fun phase3                                                                                  # PUBLIC. PHASE 3: Accept our Imports, then wait for Run_Gun to fire.
                                (
                                  imports:      Imports,
                                  run_gun':     Run_Gun,        
                                  end_gun':     End_Gun
                                )
                                =
                                {
                                    put_in_mailslot  (me_slot, { me, millboss_arg, imports, run_gun', end_gun' });
                                };

                            (exports, phase3);
                        };
            };
    };

end;





Comments and suggestions to: bugs@mythryl.org

PreviousUpNext