Scripts/HiveOff

From J Wiki
Jump to: navigation, search

Hiving off the JWD interface

This is a technique to split off a wd-based UI into a separate app.

The "glue" for the 2 parts of your app: File:Facelink.ijs

The "duty cycle" supporting facelink.ijs (residing in _z_ locale): File:Faceduty.ijs

Rationale

J701 was released in Jan 2011 without support for the popular wd-based interface (11!:0) of j602. Users were expected to port their own j602 apps to use either JGTK or the JHS framework.

This policy, perfectly justifiable though it is from the vendor's pov, can leave a critical utility stranded in the world of j602. Converting an app to the JHS framework (or to JGTK) is not straightforward and can necessitate an entire reimplementation of the script.

Bill Lam has recently released a prototype wd-emulator for JGTK (Feb 2012) which will make porting an app a lot easier.

Why might you want to hive-off a script's UI as a separate asynchronous app?

  • As a stop-gap measure to get a key j602 script operating under j701
  • To preserve a finely-tuned j602 UI in the j7 environment (JHS or JGTK)
  • As an intermediate stage to replacing a wd-based UI with one written in Cocoa / Objective-C (the pro language for UI implementation on the Apple Macintosh)
  • to build a collection of stock reusable UIs which are independent of the JVERSION hosting the app proper.

Case Study

File:Tempcon.ijs is a straightforward JWD sample script built using j602's Form Editor.

Three fields, Celsius, Fahrenheit and Kelvin, accept new numerals. Whichever field is edited, pressing Enter will update the other two accordingly.

A further edit field shows an integer: the decimal places in the temperature fields. It can be overtyped, or two arrow buttons step it.

Tempcon.jpg

We split it into two communicating parts: File:Tempface.ijs (the bare UI) and File:Tempserv.ijs (the faceless app).

Note that the wd-calls comprising the UI have not actually been stripped out of tempserv.ijs, but merely disabled. This is recommended to retain comparative diagnostics during development.

To run the two parts, load them into distinct independent j602 sessions. Both scripts run on load. They communicate via two scripts which are written and refreshed by the two apps: ~temp/face_in.ijs and ~temp/face_out.ijs. The boxed names of the two files are the nouns: FACEIN and FACEOUT, and that is how we'll refer to the files.

Method

Determine the wd-buffers which define the state of the UI

In this example the important buffers are degc degf degk sig, to which we add: TITLE.

Ensure that these buffers are kept in-step with their controls. (In a typical JWD UI these are allowed to get out-of-step, because it is thought not to matter.) To do this, examine every wd-statement and ensure that both screen and buffer are updated together.

For example:

updeg=: 3 : 0
	NB. update degree fields
'C F K'=. 3 {. y
wd 'psel fm; set degc *',degc=: dp C
wd 'psel fm; set degf *',degf=: dp F
wd 'psel fm; set degk *',degk=: dp K
)

instead of:

updeg=: 3 : 0
	NB. update degree fields
'C F K'=. 3 {. y
wd 'psel fm; set degc *',dp C
wd 'psel fm; set degf *',dp F
wd 'psel fm; set degk *',dp K
)

Split the script

Make 2 copies of tempcon.ijs, renaming them tempserv.ijs and tempface.ijs. Place at the head of each script:

load '~proj/facelink.ijs'
BUFALL=: 'degc degf degk sig TITLE'
CLIENT_z_=: 0   NB. in tempface.ijs this should be CLIENT_z_=: 1

Rewrite fm_run in each script

Modify the UI-creating verb: fm_run as appropriate for the two scripts.

In tempcon.ijs fm_run was originally like this:

fm_run=: 3 : 0
	NB. Init and run the app
fm_close''
wd FM
NB. initialize form here
(BUFALL)=: <''
wd 'set sig *',sig=: ,'2'
changec 100
wd 'pshow;'
wd 'psel fm; pn *',TITLE=: 'Temp Conversion'
)

In tempserv.ijs rewrite fm_run

  • to disable creation of the UI (fm)
  • insert user hooks in the code of faceduty.ijs (loaded by: facelink.ijs) by overriding the verbs die_z_ and doframe_z_, and possibly also resurrect_z_.
  • start the duty cycle with run'' (equivalent to duty_cycle_z_ 1)
fm_run=: 3 : 0
	NB. Init and run the app
NB. fm_close''
wd=. empty
wd FM
NB. initialize form here
BUFALL=: 'degc degf degk sig TITLE'
(BUFALL)=: <''
wd 'set sig *',sig=: ,'2'
changec 100
wd 'pshow;'
wd 'psel fm; pn *',TITLE=: 'Temp Conversion'
die_z_=: mydie_base_
doframe_z_=: mydoframe_base_
run''
)

In tempface.ijs rewrite fm_run

  • to initialise the buffers in a more useful way (for test purposes)
  • insert user hooks in the code of faceduty.ijs (loaded by: facelink.ijs) by overriding the verbs die_z_ and doframe_z_, and possibly also resurrect_z_.
  • start the duty cycle with run'' (equivalent to duty_cycle_z_ 1)
fm_run=: 3 : 0
	NB. Init and run the app
TITLE=: '(fm)'
doframe_z_=: mydoframe__
fm_close''
wd FM
NB. initialize form here
(BUFALL)=: <'<UNSET>'
NB. wd 'set sig *',sig=: ,'2'
NB. changec 100
wd 'pshow;'
wd 'psel fm; pn *',TITLE=: 'Temp Conversion'
run''
)

Provide a verb (wup) to refresh the screen

In tempface.ijs write a verb wup to take each of the main buffers and write their contents back to the screen.

This verb will be called by doframe_z_ in the duty cycle.

wup=: 3 : 0
	NB. update window from bufs
try. wd 'psel fm' catch. return. end.
wd 'set degc *',degc
wd 'set degf *',degf
wd 'set degk *',degk
wd 'set sig *',sig
)

Write the code-hooks for the duty cycle

The main code-hook is the verb: mydoframe.

In tempface.ijs this takes the form:

mydoframe=: 3 : 0
	NB. locale version
if. (y-:1) or xin'' do.
  load FACEIN
  xin 0
  wup''
end.
try. wd 'psel fm; pn *',TITLE,brack CYCLE  catch. end.
)

In tempserv.ijs this takes the form:

mydoframe=: 3 : 0
	NB. tempserv version, overrides doframe_z_
if. y-:_1 do.		NB. last pass (exit)
  HARDLOOP_z_=: 0	NB. to stop hardloop if in use
  return.
end.
	NB. service new message (FACEOUT) from loface ...
if. (y-:1) or xout'' do.
  xout 0
  load FACEOUT		NB. receive bufs+instr from face
  sendlink ''		NB. send back updated bufs
end.
)

Modify the control handlers

The control handlers must be altered in tempface.ijs to talk to the server instead of running verbs in the face app.

The example should make the task clear:

NB. fm_sigup_button=:	stepup
NB. fm_sigdown_button=:	stepdown
NB. fm_degf_button=:	changef
NB. fm_degc_button=:	changec
NB. fm_degk_button=:	changek
NB. fm_sig_button=:	stepnone

fm_sigup_button=:	sendlink bind 'stepup'
fm_sigdown_button=:	sendlink bind 'stepdown'
fm_degf_button=:	sendlink bind 'changef'
fm_degc_button=:	sendlink bind 'changec'
fm_degk_button=:	sendlink bind 'changek'
fm_sig_button=:		sendlink bind 'stepnone'

How it works

Both halves of the old app use the same "glue" script(s), referring to CLIENT_z_ to determine whether it is the server or the hived-off UI.

But each half works the same way. We will describe it for tempface.ijs:

  • Write the buffer contents to the script FACEOUT (using verb: bufscript)
  • Flag FACEOUT as "to-go" (using: xout 1)
  • Watch for the other script (FACEIN) to be flagged "to-go", then read the script and flag it as read (using: xin 0)