Module: environment-protocols Synopsis: Environment protocols Author: Andy Armstrong Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// Application objects define constant = one-of(#"uninitialized", #"running", #"stopped", #"closed"); define constant = one-of(#"start", #"debug", #"interact"); define open abstract primary class (, ) sealed constant slot server-project :: , required-init-keyword: project:; sealed constant slot application-object-table :: = make-object-cache(); slot application-client :: , required-init-keyword: client:; slot application-machine :: , required-init-keyword: machine:; slot application-filename :: , required-init-keyword: filename:; slot application-arguments :: = "", init-keyword: arguments:; slot application-state :: = #"uninitialized", setter: %state-setter; slot application-temporary-stop? :: = #f; end class ; define method application-state-setter (state :: , application :: ) => (state :: ) unless (state == application.application-state) application.%state := state; unless (application.application-temporary-stop?) broadcast($project-channel, make(, project: application.server-project)) end end; state end method application-state-setter; define macro with-application-transaction { with-application-transaction (?server:expression) ?body:body end } => { perform-application-transaction (?server, method () ?body end) } end macro with-application-transaction; define open generic note-application-initialized (project :: ) => (); define open generic perform-application-transaction (server :: , function :: ) => (#rest values); define open generic application-threads (application :: , #key client) => (threads :: ); define open generic application-running? (application :: ) => (running? :: ); define open generic application-stopped? (application :: ) => (stopped? :: ); define open generic application-closed? (application :: ) => (closed? :: ); define open generic find-application-proxy (application :: , id :: ) => (application-proxy); define open generic application-proxy-id (application :: , proxy) => (id :: false-or()); define open generic application-debug? (server :: ) => (debug? :: ); define open generic application-pause-before-termination? (server :: ) => (well? :: ); define open generic application-startup-option (server :: ) => (opt :: ); define open generic application-machine (server :: ) => (machine :: false-or()); define open generic application-stop-reason-message (application :: ) => (message :: false-or()); /// Application handling define open generic run-application (server :: , #key startup-option, client, filename, arguments, pause-before-termination?, library-search-paths, working-directory, machine) => (application :: ); define open generic initialize-application-client (client :: , application :: ) => (); // There's no NOTE-RUN-APPLICATION-REQUESTED, we just do the relevant // work in RUN-APPLICATION. define open generic note-run-application-failed (application :: ) => (); define open generic update-application (server :: , #key progress-callback) => (); define open generic continue-application (server :: , #key thread) => (); define open generic stop-application (server :: , #key client-data) => (); define open generic close-application (server :: , #key wait-for-termination? :: ) => (); /// MAKE-PROJECT-APPLICATION (environment-protocols) define open generic make-project-application (project :: , #key, #all-keys) => (application :: ); /// Application state implementation define method application-running? (application :: ) => (running? :: ) application-state(application) = #"running" end method application-running?; define method application-stopped? (application :: ) => (stopped? :: ) application-state(application) = #"stopped" end method application-stopped?; define method application-closed? (application :: ) => (closed? :: ) application-state(application) = #"closed" end method application-closed?; define function application-tethered? (server :: ) => (tethered? :: ) let project = server.server-project; let application = project.project-application; application & ~application.application-closed? end function application-tethered?; /// Implementation define method perform-application-transaction (project :: , function :: ) => (#rest values) let application = project.project-application; if (application) perform-application-transaction(application, function) else function() end end method perform-application-transaction; define method initialize-application-client (client :: , application :: ) => () #f end method initialize-application-client; define method note-application-initialized (project :: ) => () broadcast($project-channel, make(, project: project)); end method note-application-initialized; define method choose-server (project :: , application :: , #key error?, default-server) => (application :: ) ignore(error?, default-server); application end method choose-server; define method record-client-query (application :: , client, object :: , type :: ) => () record-client-query(server-project(application), client, object, type) end method record-client-query; define method environment-object-type-name (object :: ) => (label :: ) "Application" end method environment-object-type-name; define method lookup-environment-object-by-proxy (application :: , proxy) => (object :: false-or()) element(application-object-table(application), proxy, default: #f) end method lookup-environment-object-by-proxy; define method cache-environment-object (application :: , proxy, object :: ) => (object :: ) element(application-object-table(application), proxy) := object end method cache-environment-object; define method environment-object-home-server? (application :: , object :: ) => (home? :: ) let proxy = application-object-proxy(object); proxy & lookup-environment-object-by-proxy(application, proxy) & #t end method environment-object-home-server?; define method application-debug? (project :: ) => (debug? :: ) let application = project.project-application; application & application-debug?(application); end method; define method application-pause-before-termination? (project :: ) => (pause? :: ) let application = project.project-application; application & application-pause-before-termination?(application); end method; define method application-startup-option (project :: ) => (opt :: ); let application = project.project-application; if (application) application-startup-option(application) else // TODO: Signal some kind of error! // This is overly-defensive programming, and I deserve to die // for this. #"start" end if end method; /// RUN-APPLICATION (environment-protocols) define method run-application (project :: , #key startup-option = #"start", client = project, filename, arguments, working-directory, pause-before-termination? = project.project-default-pause-before-termination?, share-console? = #f, library-search-paths = vector(release-runtime-directory()), machine = unsupplied()) => (application :: ) let machine :: = if (supplied?(machine)) machine else environment-host-machine() end; unless (filename & arguments & working-directory) let (f, a, wd) = project-debug-options(project); filename := filename | f; arguments := arguments | a; working-directory := working-directory | wd; end; let application = project-application(project); if (application) assert(application.application-closed?, "Attempting to restart an already running application: %s", filename); application-filename(application) := filename; application-arguments(application) := arguments; application-machine(application) := machine; application-client(application) := client else application := make-project-application(project, client: client, machine: machine, filename: filename, arguments: arguments); project-application(project) := application; end if; broadcast($project-channel, make(, project: project)); initialize-application-client(client, application); let host-machine? = machine == environment-host-machine(); run-application(application, startup-option: startup-option, client: client, filename: filename, arguments: arguments, machine: machine, library-search-paths: if (host-machine?) library-search-paths else #[] end, working-directory: if (host-machine?) working-directory | project.project-bin-directory end, share-console?: share-console?, pause-before-termination?: pause-before-termination?); end method run-application; define constant $dll-wrap-application = "bin/dll-wrap.exe"; define function project-debug-options (project :: ) => (filename :: , arguments :: , working-directory :: false-or()) let build-filename = project.project-build-filename; let bin-directory = project.project-bin-directory; let build-location = merge-locators(build-filename, bin-directory); let debug-filename = project.project-debug-filename; let debug-arguments = project.project-debug-arguments; let target-type = project.project-target-type; case debug-filename => values(debug-filename, debug-arguments, #f); project.playground-project? => let filename = project.playground-application-filename | build-location; values(filename, debug-arguments, filename.locator-directory); target-type == #"executable" => values(build-location, debug-arguments, #f); target-type == #"dll" => let directory = as(, release-directory()); values(merge-locators(as(, $dll-wrap-application), directory), format-to-string("\"%s\"", as(, build-location)), #f); otherwise => error("The project is neither a DLL nor an EXE?"); end end function project-debug-options; define function project-default-pause-before-termination? (project :: ) => (pause-before-termination? :: ) project.project-interface-type == #"console" & project.project-target-type == #"executable" end function project-default-pause-before-termination?; //---*** hughg: Could add a method in dfmc-environment-application for //---*** which does the cleanup work currently done //---*** just before it calls this. define method note-run-application-failed (application :: ) => () let project = application.server-project; broadcast($project-channel, make(, project: project)) end method note-run-application-failed; define method continue-application (project :: , #key thread) => () let application = project-application(project); application & continue-application(application, thread: thread) end method continue-application; define method stop-application (project :: , #key client-data = #f) => () let application = project-application(project); application & stop-application(application, client-data: client-data) end method stop-application; define method close-application (project :: , #key wait-for-termination? :: ) => () let application = project-application(project); application & close-application(application, wait-for-termination?: wait-for-termination?) end method close-application; define method update-application (project :: , #key progress-callback) => () let application = project-application(project); application & update-application(application, progress-callback: progress-callback) end method update-application; /// Proxy handling //--- This default method means that the objects will never get linked, so //--- we need a real solution in the application server implementation. define method find-application-proxy (application :: , id :: ) => (application-proxy) #f end method find-application-proxy; define function ensure-application-proxy (application :: , object :: ) => (proxy) application-object-proxy(object) | begin let project = server-project(application); let id = environment-object-id(project, object); if (instance?(id, )) let proxy = find-application-proxy(application, id); if (proxy) application-object-proxy(object) := proxy end end end end function ensure-application-proxy;