Previous Up Next

6  The Render modules

6.1  Master side

The iterator on scenes in defined in the Scene module. For simplicity we present iteration by the line.

type elt = string * int and enum = string * int let start sc = (sc.file, sc.ht-1) let step (file,n) = if n < 0 then None else Some ((file,n),(file,(n-1)))

The master uses the fold pool of section 4.2, of which register function is stored in the local name service.

let img_pool = create_pool () let () = Join.Ns.register Join.Ns.here "reg" (img_pool.register : (Scene.elt -> int * string) Join.chan)

As specified by the type constraint above, remote workers return pairs of a line number and of a line of pixels.

Finally, the following function commands the rendering of scene sc and saves the resulting image.

let render_image sc = let img = Array.create sc.ht "" in let combine (n.line) = img.(n) <- line in img_pool.fold sc combine () ; save_image sc.file img

Notice that the combination function performs an update of the bitmap img. It should be noticed that render_image above returns only when the image is saved. We can then define RenderMaster.render as being render_image.

let render = render_image

As a consequence, all images are saved when the interpreter terminates and the master can terminate.

In our implementation, we in fact save the image asynchronously, for disk operations not to delay the interpreter. Termination of the master is then controlled by a simple monitor (page ??) that counts images.

let monitor = create_monitor (fun () r -> 1+r) 0 let render_image sc = monitor.enter () ; let img = Array.create sc.ht "" in let combine (n.line) = img.(n) <- line in img_pool.fold sc combine () ; spawn begin save_image sc.file img ; monitor.leave () end

The monitor is made public by the module RenderMaster, for the code after the call to the interpreter to wait on it.

(* Call interpreter, gml is the abstract syntax tree *) let module E = Eval.Make(RenderMaster) in E.eval_program gml ; (* Interpreter is over *) let m = RenderMaster.monitor in spawn m.finished() ; let nimages = m.wait() in Printf.eprintf "My slaves have computed %d images\n" nimages ; exit 0

6.2  Slave side

A slave executes two tasks concurrently. It (1) interprets the Gml program so as to build the scenes and (2) computes subimages on master demand. For the two agents to communicate, we introduce a suitable mapping from filenames to scenes.

type ('a,'b) hashtbl = { find : 'a -> 'b; add : ('a * 'b) -> unit; } let create_hashtbl () = let t = Hashtbl.create 17 in def state(blocked) & add(k,v) = Hashtbl.add t k v ; List.iter (fun release -> spawn release()) blocked ; state([]) & reply () to add or state(blocked) & find(k) = let v = try Some (Hashtbl.find t k) with Not_found -> None in match v with | Some v -> state(blocked) & reply v to find | None -> def release() & wait() = reply () to wait in state(release::blocked) & reply wait() ; find(k) to find in spawn state([]) ; { find=find; add=add; } let scenes = create_hashtbl ()

The code above is a wrapper of the (OCaml) hashtable t. There are two points to notice. First, by very existence of state the internal hashtable t is protected against concurrent modification, as usual. Second, the synchronous channel find blocks when the table t does not associate any value to the key k, until a value for k is added into the table. This block/release process implies a form of communication between find and add operations. Such a communication is by the means of the message that is pending over the internal channel state.

More precisely, the code of find first changes the interface to Hashtbl.find “return value v or raise exception Not_found” into the new interface “return Some v or return None”. In the first, successful, case the caller of find gets v as a reply. In the second, failed, case the reply to find is delayed, by inserting a call wait() before calling find again. The call to wait will return when a message is sent on the asynchronous channel release, which the code for add does. A find operation is then attempted again. The coding is slightly inefficient, but it suffices here. Namely, the pool of the master does not allow several concurrent calls to find in a given slave. Thus the list blocked cannot hold more than one element.

Actual subimage computations are performed by the following definition.

def compute_subimage (tag,n) = let pxls = Ray.render_line (scenes.find tag) n in reply (n,pxls) to compute_subimage

Additionally, compute_subimage is registered into the pool of the master, we omit the code which is the same as the one of section 4.3. Finally, RenderSlave.render simply stores the scene at the intention of compute_subimage.

let render sc = scenes.add (sc.file,sc)

As regards termination, the simplest solution is for slaves to terminate when the master does — see the end of section 4.3.


Previous Up Next