/* Copyright (C) 1998, 1999 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gdevprna.c,v 1.2.6.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/* Generic asynchronous printer driver support */

/* Initial version 2/1/98 by John Desrosiers (soho@crl.com) */
/* Revised 8/7/98 by L. Peter Deutsch (ghost@aladdin.com) for */
/*   memory manager changes */
/* 12/1/98 soho@crl.com - Removed unnecessary flush & reopen in */
/*         gdev_prn_async_write_get_hardware_params */
#include "gdevprna.h"
#include "gsalloc.h"
#include "gsdevice.h"
#include "gsmemlok.h"
#include "gsmemret.h"
#include "gsnogc.h"
#include "gxcldev.h"
#include "gxclpath.h"
#include "gxpageq.h"
#include "gzht.h"		/* for gx_ht_cache_default_bits */

/* ----------------- Constants ----------------------- */
/*
 * Fixed overhead # bytes to run renderer in (+ driver-spec'd variable bytes):
 * empirical & still very subject to change.
 */
#define RendererAllocationOverheadBytes 503000 /* minimum is 503,000 as of 4/26/99 */

#ifdef DEBUG
/* 196000 is pretty much the minimum, given 16K phys memfile blocks */
/*# define DebugBandlistMemorySize 196000*/ /* comment out to disable fixed (debug) bandlist size */
#endif /* defined(DEBUG) */

/* ---------------- Standard device procedures ---------------- */
private dev_proc_close_device(gdev_prn_async_write_close_device);
private dev_proc_output_page(gdev_prn_async_write_output_page);
private dev_proc_put_params(gdev_prn_async_write_put_params);
private dev_proc_get_hardware_params(gdev_prn_async_write_get_hardware_params);
private dev_proc_put_params(gdev_prn_async_render_put_params);

/* ---------------- Forward Declarations ---------------------- */
private void gdev_prn_dealloc(P1(gx_device_printer *));
private proc_free_up_bandlist_memory(gdev_prn_async_write_free_up_bandlist_memory);
private int flush_page(P2(gx_device_printer *, bool));
private int reopen_clist_after_flush(P1(gx_device_printer *));
private void reinit_printer_into_printera(P1(gx_device_printer * const));
private int alloc_bandlist_memory(P2(gs_memory_t **, gs_memory_t *));
private void free_bandlist_memory(P1(gs_memory_t *));
private int alloc_render_memory(P3(gs_memory_t **, gs_memory_t *, long));
private void free_render_memory(P1(gs_memory_t *));
private gs_memory_recover_status_t
    prna_mem_recover(P2(gs_memory_retrying_t *rmem, void *proc_data));

/* ------ Open/close ------ */

/*
 * Open this printer device in ASYNC (overlapped) mode.
 * This routine must always called by the concrete device's xx_open routine
 * in lieu of gdev_prn_open.
 */
int
gdev_prn_async_write_open(gx_device_printer * pwdev, int max_raster,
			  int min_band_height, int max_src_image_row)
{
    gx_device *const pdev = (gx_device *) pwdev;
    int code;
    bool writer_is_open = false;
    gx_device_clist_writer *const pcwdev =
	&((gx_device_clist *) pwdev)->writer;
    gx_device_clist_reader *pcrdev = 0;
    gx_device_printer *prdev = 0;
    gs_memory_t *render_memory = 0;	/* renderer's mem allocator */

    pwdev->page_queue = 0;
    pwdev->bandlist_memory = 0;
    pwdev->async_renderer = 0;

    /* allocate & init render memory */
    /* The big memory consumers are: */
    /* - the buffer used to read images from the command list */
    /* - buffer used by gx_real_default_strip_copy_rop() */
    /* - line pointer tables for memory devices used in plane extraction */
    /* - the halftone cache */
    /* - the band rendering buffer */
    /* The * 2's in the next statement are a ****** HACK ****** to deal with */
    /* sandbars in the memory manager. */
    if ((code = alloc_render_memory(&render_memory,
	    &gs_memory_default, RendererAllocationOverheadBytes + max_raster
				    /* the first * 2 is not a hack */
		   + (max_raster + sizeof(void *) * 2) * min_band_height
		   + max_src_image_row + gx_ht_cache_default_bits() * 2)) < 0)
	     goto open_err;

    /* Alloc & init bandlist allocators */
    /* Bandlist mem is threadsafe & common to rdr/wtr, so it's used */
    /* for page queue & cmd list buffers. */
    if ((code = alloc_bandlist_memory
	 (&pwdev->bandlist_memory, &gs_memory_default)) < 0)
	goto open_err;

    /* Dictate banding parameters for both renderer & writer */
    /* Protect from user change, since user changing these won't be */
    /* detected, ergo the necessary close/reallocate/open wouldn't happen. */
    pwdev->space_params.banding_type = BandingAlways;
    pwdev->space_params.params_are_read_only = true;

    /* Make a copy of device for use as rendering device b4 opening writer */
    code = gs_copydevice((gx_device **) & prdev, pdev, render_memory);
    pcrdev = &((gx_device_clist *) prdev)->reader;
    if (code < 0)
	goto open_err;

    /* -------------- Open cmd list WRITER instance of device ------- */
    /* --------------------------------------------------------------- */
    /* This is wrong, because it causes the same thing in the renderer */
    pwdev->OpenOutputFile = 0;	/* Don't open output file in writer */

    /* Hack: set this vector to tell gdev_prn_open to allocate for async rendering */
    pwdev->free_up_bandlist_memory = &gdev_prn_async_write_free_up_bandlist_memory;

    /* prevent clist writer from queuing path graphics & force it to split images */
    pwdev->clist_disable_mask |= clist_disable_fill_path |
	clist_disable_stroke_path | clist_disable_complex_clip |
	clist_disable_nonrect_hl_image | clist_disable_pass_thru_params;

    if ((code = gdev_prn_open(pdev)) >= 0) {
	writer_is_open = true;

	/* set up constant async-specific fields in device */
	reinit_printer_into_printera(pwdev);

	/* keep ptr to renderer device */
	pwdev->async_renderer = prdev;

	/* Allocate the page queue, then initialize it */
	/* Use bandlist memory since it's shared between rdr & wtr */
	if ((pwdev->page_queue = gx_page_queue_alloc(pwdev->bandlist_memory)) == 0)
	    code = gs_note_error(gs_error_VMerror);
	else
	    /* Allocate from clist allocator since it is thread-safe */
	    code = gx_page_queue_init(pwdev->page_queue, pwdev->bandlist_memory);
    }
    /* ------------ Open cmd list RENDERER instance of device ------- */
    /* --------------------------------------------------------------- */
    if (code >= 0) {
	gx_semaphore_t *open_semaphore;

	/* Force writer's actual band params into reader's requested params */
	prdev->space_params.band = pcwdev->page_info.band_params;

	/* copydevice has already set up prdev->memory = render_memory */
	/* prdev->bandlist_memory = pwdev->bandlist_memory; */
	prdev->buffer_memory = prdev->memory;

	/* enable renderer to accept changes to params computed by writer */
	prdev->space_params.params_are_read_only = false;

	/* page queue is common to both devices */
	prdev->page_queue = pwdev->page_queue;

	/* Start renderer thread & wait for its successful open of device */
	if (!(open_semaphore = gx_semaphore_alloc(&gs_memory_default)))
	    code = gs_note_error(gs_error_VMerror);
	else {
	    gdev_prn_start_render_params thread_params;

	    thread_params.writer_device = pwdev;
	    thread_params.open_semaphore = open_semaphore;
	    thread_params.open_code = 0;
	    code = (*pwdev->printer_procs.start_render_thread)
		(&thread_params);
	    if (code >= 0)
		gx_semaphore_wait(open_semaphore);
	    code = thread_params.open_code;
	    gx_semaphore_free(open_semaphore);
	}
    }
    /* ----- Set the recovery procedure for the mem allocator ----- */
    if (code >= 0) {
	gs_memory_retrying_set_recover(
		(gs_memory_retrying_t *)&gs_memory_default,
		prna_mem_recover,
		(void *)pcwdev
	    );
    }
    /* --------------------- Wrap up --------------------------------- */
    /* --------------------------------------------------------------- */
    if (code < 0) {
open_err:
	/* error mop-up */
	if (render_memory && !prdev)
	    free_render_memory(render_memory);

	gdev_prn_dealloc(pwdev);
	if (writer_is_open) {
	    gdev_prn_close(pdev);
	    pwdev->free_up_bandlist_memory = 0;
	}
    }
    return code;
}

/* This procedure is called from within the memory allocator when regular */
/* malloc's fail -- this procedure tries to free up pages from the queue  */
/* and returns a status code indicating whether any more can be freed.    */
private gs_memory_recover_status_t
prna_mem_recover(gs_memory_retrying_t *rmem, void *proc_data)
{
    int pages_remain = 0;
    gx_device_clist_writer *cldev = proc_data;

    if (cldev->free_up_bandlist_memory != NULL)
	pages_remain =
	    (*cldev->free_up_bandlist_memory)( (gx_device *)cldev, false );
    return (pages_remain > 0) ? RECOVER_STATUS_RETRY_OK : RECOVER_STATUS_NO_RETRY;
}

/* (Re)set printer device fields which get trampled by gdevprn_open & put_params */
private void
reinit_printer_into_printera(
			     gx_device_printer * const pdev	/* printer to convert */
)
{
    /* Change some of the procedure vector to point at async procedures */
    /* Originals were already saved by gdev_prn_open */
    if (dev_proc(pdev, close_device) == gdev_prn_close)
	set_dev_proc(pdev, close_device, gdev_prn_async_write_close_device);
    set_dev_proc(pdev, output_page, gdev_prn_async_write_output_page);
    set_dev_proc(pdev, put_params, gdev_prn_async_write_put_params);
    set_dev_proc(pdev, get_xfont_procs, gx_default_get_xfont_procs);
    set_dev_proc(pdev, get_xfont_device, gx_default_get_xfont_device);
    set_dev_proc(pdev, get_hardware_params, gdev_prn_async_write_get_hardware_params);

    /* clist writer calls this if it runs out of memory & wants to retry */
    pdev->free_up_bandlist_memory = &gdev_prn_async_write_free_up_bandlist_memory;
}

/* Generic closing for the writer device. */
private int
gdev_prn_async_write_close_device(gx_device * pdev)
{
    gx_device_printer *const pwdev = (gx_device_printer *) pdev;

    /* Signal render thread to close & terminate when done */
    gx_page_queue_add_page(pwdev->page_queue,
			   GX_PAGE_QUEUE_ACTION_TERMINATE, 0, 0);

    /* Wait for renderer to finish all pages & terminate req */
    gx_page_queue_wait_until_empty(pwdev->page_queue);

    /* Cascade down to original close rtn */
    gdev_prn_close(pdev);
    pwdev->free_up_bandlist_memory = 0;

    /* Deallocte dynamic stuff */
    gdev_prn_dealloc(pwdev);
    return 0;
}

/* Deallocte dynamic memory attached to device. Aware of possible imcomplete open */
private void
gdev_prn_dealloc(gx_device_printer * pwdev)
{
    gx_device_printer *const prdev = pwdev->async_renderer;

    /* Delete renderer device & its memory allocator */
    if (prdev) {
	gs_memory_t *render_alloc = prdev->memory;

	gs_free_object(render_alloc, prdev, "gdev_prn_dealloc");
	free_render_memory(render_alloc);
    }
    /* Free page queue */
    if (pwdev->page_queue) {
	gx_page_queue_dnit(pwdev->page_queue);
	gs_free_object(pwdev->bandlist_memory, pwdev->page_queue,
		       "gdev_prn_dealloc");
	pwdev->page_queue = 0;
    }
    /* Free memory bandlist allocators */
    if (pwdev->bandlist_memory)
	free_bandlist_memory(pwdev->bandlist_memory);
}

/* Open the render portion of a printer device in ASYNC (overlapped) mode.

 * This routine is always called by concrete device's xx_open_render_device
 * in lieu of gdev_prn_open.
 */
int
gdev_prn_async_render_open(gx_device_printer * prdev)
{
    gx_device *const pdev = (gx_device *) prdev;

    prdev->is_async_renderer = true;
    return gdev_prn_open(pdev);
}

/* Generic closing for the rendering device. */
int
gdev_prn_async_render_close_device(gx_device_printer * prdev)
{
    gx_device *const pdev = (gx_device *) prdev;

    return gdev_prn_close(pdev);
}

/* (Re)set renderer device fields which get trampled by gdevprn_open & put_params */
private void
reinit_printer_into_renderer(
			     gx_device_printer * const pdev	/* printer to convert */
)
{
    set_dev_proc(pdev, put_params, gdev_prn_async_render_put_params);
}

/* ---------- Start rasterizer thread ------------ */
/*
 * Must be called by async device driver implementation (see gdevprna.h
 * under "Synchronizing the Instances"). This is the rendering loop, which
 * requires its own thread for as long as the device is open. This proc only
 * returns after the device is closed, or if the open failed. NB that an
 * open error leaves things in a state where the writer thread will not be
 * able to close since it's expecting the renderer to acknowledge its
 * requests before the writer can close.  Ergo, if this routine fails you'll
 * crash unless the caller fixes the problem & successfully retries this.
 */
int				/* rets 0 ok, -ve error code if open failed */
gdev_prn_async_render_thread(
			     gdev_prn_start_render_params * params
)
{
    gx_device_printer *const pwdev = params->writer_device;
    gx_device_printer *const prdev = pwdev->async_renderer;
    gx_page_queue_entry_t *entry;
    int code;

    /* Open device, but don't use default if user didn't override */
    if (prdev->printer_procs.open_render_device ==
	  gx_default_open_render_device)
	code = gdev_prn_async_render_open(prdev);
    else
	code = (*prdev->printer_procs.open_render_device) (prdev);
    reinit_printer_into_renderer(prdev);

    /* The cmd list logic assumes reader's & writer's tile caches are same size */
    if (code >= 0 &&
	  ((gx_device_clist *) pwdev)->writer.page_tile_cache_size !=
	    ((gx_device_clist *) prdev)->writer.page_tile_cache_size) {
	gdev_prn_async_render_close_device(prdev);
	code = gs_note_error(gs_error_VMerror);
    }
    params->open_code = code;
    gx_semaphore_signal(params->open_semaphore);
    if (code < 0)
	return code;

    /* fake open, since not called by gs_opendevice */
    prdev->is_open = true;

    /* Successful open */
    while ((entry = gx_page_queue_start_dequeue(prdev->page_queue))
	   && entry->action != GX_PAGE_QUEUE_ACTION_TERMINATE) {
	/* Force printer open again if it mysteriously closed. */
	/* This shouldn't ever happen, but... */
	if (!prdev->is_open) {
	    if (prdev->printer_procs.open_render_device ==
		  gx_default_open_render_device)
		code = gdev_prn_async_render_open(prdev);
	    else
		code = (*prdev->printer_procs.open_render_device) (prdev);
	    reinit_printer_into_renderer(prdev);

	    if (code >= 0) {
		prdev->is_open = true;
		gdev_prn_output_page((gx_device *) prdev, 0, true);
	    }
	}
	if (prdev->is_open) {
	    /* Force retrieved entry onto render device */
	    ((gx_device_clist *) prdev)->common.page_info = entry->page_info;

	    /* Set up device geometry */
	    if (clist_setup_params((gx_device *) prdev) >= 0)
		/* Go this again, since setup_params may have trashed it */
		((gx_device_clist *) prdev)->common.page_info = entry->page_info;

	    /* Call appropriate renderer routine to deal w/buffer */
	    /* Ignore status, since we don't know how to deal w/errors! */
	    switch (entry->action) {

		case GX_PAGE_QUEUE_ACTION_FULL_PAGE:
		    (*dev_proc(prdev, output_page))((gx_device *) prdev,
						    entry->num_copies, true);
		    break;

		case GX_PAGE_QUEUE_ACTION_PARTIAL_PAGE:
		case GX_PAGE_QUEUE_ACTION_COPY_PAGE:
		    (*dev_proc(prdev, output_page))((gx_device *) prdev,
						    entry->num_copies, false);
		    break;
	    }
	    /*
	     * gx_page_queue_finish_dequeue will close and free the band
	     * list files, so we don't need to call clist_close_output_file.
	     */
	}
	/* Finalize dequeue & free retrieved queue entry */
	gx_page_queue_finish_dequeue(entry);
    }

    /* Close device, but don't use default if user hasn't overriden. */
    /* Ignore status, since returning bad status means open failed */
    if (prdev->printer_procs.close_render_device ==
	  gx_default_close_render_device)
	gdev_prn_async_render_close_device(prdev);
    else
	(*prdev->printer_procs.close_render_device)(prdev);

    /* undo fake open, since not called by gs_closedevice */
    prdev->is_open = false;

    /* Now that device is closed, acknowledge gx_page_queue_terminate */
    gx_page_queue_finish_dequeue(entry);

    return 0;
}

/* ------ Get/put parameters ------ */

/* Put parameters. */
private int
gdev_prn_async_write_put_params(gx_device * pdev, gs_param_list * plist)
{
    gx_device_clist_writer *const pclwdev =
	&((gx_device_clist *) pdev)->writer;
    gx_device_printer *const pwdev = (gx_device_printer *) pdev;
    gdev_prn_space_params save_sp = pwdev->space_params;
    int save_height = pwdev->height;
    int save_width = pwdev->width;
    int code, ecode;

    if (!pwdev->is_open)
	return (*pwdev->orig_procs.put_params) (pdev, plist);

    /*
     * First, cascade to real device's put_params.
     * If that put_params made any changes that require re-opening
     * the device, just flush the page; the parameter block at the
     * head of the next page will reflect the changes just made.
     * If the put_params requires no re-open, just slip it into the
     * stream in the command buffer. This way, the
     * writer device should parallel the renderer status at the same point
     * in their respective executions.
     *
     * NB. that all this works only because we take the position that
     * put_params can make no change that actually affects hardware's state
     * before the final output_page on the RASTERIZER.
     */
    /* Call original procedure, but "closed" to prevent closing device */
    pwdev->is_open = false;	/* prevent put_params from closing device */
    code = (*pwdev->orig_procs.put_params) (pdev, plist);
    pwdev->is_open = true;
    pwdev->OpenOutputFile = 0;	/* This is wrong, because it causes the same thing in the renderer */

    /* Flush device or emit to command list, depending if device changed geometry */
    if (memcmp(&pwdev->space_params, &save_sp, sizeof(save_sp)) != 0 ||
	pwdev->width != save_width || pwdev->height != save_height
	) {
	int pageq_remaining;
	int new_width = pwdev->width;
	int new_height = pwdev->height;
	gdev_prn_space_params new_sp = pwdev->space_params;

	/* Need to start a new page, reallocate clist memory */
	pwdev->width = save_width;
	pwdev->height = save_height;
	pwdev->space_params = save_sp;

	/* First, get rid of any pending partial pages */
	code = flush_page(pwdev, false);

	/* Free and reallocate the printer memory. */
	pageq_remaining = 1;	/* assume there are pages left in queue */
	do {
	    ecode =
		gdev_prn_reallocate_memory(pdev,
					   &new_sp, new_width, new_height);
	    if (ecode >= 0)
		break;		/* managed to recover enough memory */
	    if (!pdev->is_open) {
		/* Disaster! Device was forced closed, which async drivers */
		/* aren't suppsed to do. */
		gdev_prn_async_write_close_device(pdev);
		return ecode;	/* caller 'spozed to know could be closed now */
	    }
	    pclwdev->error_is_retryable = (ecode == gs_error_VMerror);
	}
	while (pageq_remaining >= 1 &&
	       (pageq_remaining = ecode =
		clist_VMerror_recover(pclwdev, ecode)) >= 0);
	if (ecode < 0) {
	    gdev_prn_free_memory(pdev);
	    pclwdev->is_open = false;
	    code = ecode;
	}
    } else if (code >= 0) {
	do
	    if ((ecode = cmd_put_params(pclwdev, plist)) >= 0)
		break;
	while ((ecode = clist_VMerror_recover(pclwdev, ecode)) >= 0);
	if (ecode < 0 && pclwdev->error_is_retryable &&
	    pclwdev->driver_call_nesting == 0
	    )
	    ecode = clist_VMerror_recover_flush(pclwdev, ecode);
	if (ecode < 0)
	    code = ecode;
    }
    /* Reset fields that got trashed by gdev_prn_put_params and/or gdev_prn_open */
    reinit_printer_into_printera(pwdev);

    return code;
}

/* Get hardware-detected params. Drain page queue, then call renderer version */
private int
gdev_prn_async_write_get_hardware_params(gx_device * pdev, gs_param_list * plist)
{
    gx_device_printer *const pwdev = (gx_device_printer *) pdev;
    gx_device_printer *const prdev = pwdev->async_renderer;

    if (!pwdev->is_open || !prdev)
	/* if not open, just use device's get hw params */
	return (dev_proc(pwdev, get_hardware_params))(pdev, plist);
    else {
	/* wait for empty pipeline */
	gx_page_queue_wait_until_empty(pwdev->page_queue);

	/* get reader's h/w params, now that writer & reader are sync'ed */
	return (dev_proc(prdev, get_hardware_params))
	    ((gx_device *) prdev, plist);
    }
}

/* Put parameters on RENDERER. */
private int		/* returns -ve err code only if FATAL error (can't keep rendering) */
gdev_prn_async_render_put_params(gx_device * pdev, gs_param_list * plist)
{
    gx_device_printer *const prdev = (gx_device_printer *) pdev;
    bool save_is_open = prdev->is_open;

    /* put_parms from clist are guaranteed to never re-init device */
    /* They're also pretty much guaranteed to always succeed */
    (*prdev->orig_procs.put_params) (pdev, plist);

    /* If device closed itself, try to open & clear it */
    if (!prdev->is_open && save_is_open) {
	int code;

	if (prdev->printer_procs.open_render_device ==
	      gx_default_open_render_device)
	    code = gdev_prn_async_render_open(prdev);
	else
	    code = (*prdev->printer_procs.open_render_device) (prdev);
	reinit_printer_into_renderer(prdev);
	if (code >= 0)
	    /****** CLEAR PAGE SOMEHOW ******/;
	else
	    return code;	/* this'll cause clist to stop processing this band! */
    }
    return 0;			/* return this unless FATAL status */
}


/* ------ Others ------ */

/* Output page causes file to get added to page queue for later rasterizing */
private int
gdev_prn_async_write_output_page(gx_device * pdev, int num_copies, int flush)
{
    gx_device_printer *const pwdev = (gx_device_printer *) pdev;
    gx_device_clist_writer *const pcwdev =
	&((gx_device_clist *) pdev)->writer;
    int flush_code;
    int add_code;
    int open_code;
    int one_last_time = 1;

    /* do NOT close files before sending to page queue */
    flush_code = clist_end_page(pcwdev);
    add_code = gx_page_queue_add_page(pwdev->page_queue,
				(flush ? GX_PAGE_QUEUE_ACTION_FULL_PAGE :
				 GX_PAGE_QUEUE_ACTION_COPY_PAGE),
				      &pcwdev->page_info, num_copies);
    if (flush && (flush_code >= 0) && (add_code >= 0)) {
	/* This page is finished */
	gx_finish_output_page(pdev, num_copies, flush);
    }

    /* Open new band files to take the place of ones added to page queue */
    while ((open_code = (*gs_clist_device_procs.open_device)
	    ((gx_device *) pdev)) == gs_error_VMerror) {
	/* Open failed, try after a page gets rendered */
	if (!gx_page_queue_wait_one_page(pwdev->page_queue)
	    && one_last_time-- <= 0)
	    break;
    }

    return
  	(flush_code < 0 ? flush_code : open_code < 0 ? open_code :
	 add_code < 0 ? add_code : 0);
}

/* Free bandlist memory waits until the rasterizer runs enough to free some mem */
private int			/* -ve err,  0 if no pages remain to rasterize, 1 if more pages to go */
gdev_prn_async_write_free_up_bandlist_memory(gx_device * pdev, bool flush_current)
{
    gx_device_printer *const pwdev = (gx_device_printer *) pdev;

    if (flush_current) {
	int code = flush_page(pwdev, true);

	if (code < 0)
	    return code;
    }
    return gx_page_queue_wait_one_page(pwdev->page_queue);
}

/* -------- Utility Routines --------- */

/* Flush out any partial pages accumulated in device */
/* LEAVE DEVICE in a state where it must be re-opened/re-init'd */
private int			/* ret 0 ok no flush, -ve error code */
flush_page(
	   gx_device_printer * pwdev,	/* async writer device to flush */
	   bool partial	/* true if only partial page */
)
{
    gx_device_clist *const pcldev = (gx_device_clist *) pwdev;
    gx_device_clist_writer *const pcwdev = &pcldev->writer;
    int flush_code = 0;
    int add_code = 0;

    /* do NOT close files before sending to page queue */
    flush_code = clist_end_page(pcwdev);
    add_code = gx_page_queue_add_page(pwdev->page_queue,
				(partial ? GX_PAGE_QUEUE_ACTION_PARTIAL_PAGE :
				 GX_PAGE_QUEUE_ACTION_FULL_PAGE),
				      &pcwdev->page_info, 0);

    /* Device no longer has BANDFILES, so it must be re-init'd by caller */
    pcwdev->page_info.bfile = pcwdev->page_info.cfile = 0;

    /* return the worst of the status. */
    if (flush_code < 0)
	return flush_code;
    return add_code;
}

/* Flush any pending partial pages, re-open device */
private int
reopen_clist_after_flush(
			 gx_device_printer * pwdev	/* async writer device to flush */
)
{
    int open_code;
    int one_last_time = 1;

    /* Open new band files to take the place of ones added to page queue */
    while ((open_code = (*gs_clist_device_procs.open_device)
	    ((gx_device *) pwdev)) == gs_error_VMerror) {
	/* Open failed, try after a page gets rendered */
	if (!gx_page_queue_wait_one_page(pwdev->page_queue)
	    && one_last_time-- <= 0)
	    break;
    }
    return open_code;
}

/*
 * The bandlist does allocations on the writer's thread & deallocations on
 * the reader's thread, so it needs to have mutual exclusion from itself, as
 * well as from other memory allocators since the reader can run at the same
 * time as the interpreter.  The bandlist allocator therefore consists of
 * a monitor-locking wrapper around either a direct heap allocator or (for
 * testing) a fixed-limit allocator.
 */

/* Create a bandlist allocator. */
private int
alloc_bandlist_memory(gs_memory_t ** final_allocator,
		      gs_memory_t * base_allocator)
{
    gs_memory_t *data_allocator = 0;
    gs_memory_locked_t *locked_allocator = 0;
    int code = 0;

#if defined(DEBUG) && defined(DebugBandlistMemorySize)
    code = alloc_render_memory(&data_allocator, base_allocator,
			       DebugBandlistMemorySize);
    if (code < 0)
	return code;
#else
    data_allocator = (gs_memory_t *)gs_malloc_memory_init();
    if (!data_allocator)
	return_error(gs_error_VMerror);
#endif
    locked_allocator = (gs_memory_locked_t *)
	gs_alloc_bytes_immovable(data_allocator, sizeof(gs_memory_locked_t),
				 "alloc_bandlist_memory(locked allocator)");
    if (!locked_allocator)
	goto alloc_err;
    code = gs_memory_locked_init(locked_allocator, data_allocator);
    if (code < 0)
	goto alloc_err;
    *final_allocator = (gs_memory_t *)locked_allocator;
    return 0;
alloc_err:
    if (locked_allocator)
	free_bandlist_memory((gs_memory_t *)locked_allocator);
    else if (data_allocator)
	gs_memory_free_all(data_allocator, FREE_ALL_EVERYTHING,
			   "alloc_bandlist_memory(data allocator)");
    return (code < 0 ? code : gs_note_error(gs_error_VMerror));
}

/* Free a bandlist allocator. */
private void
free_bandlist_memory(gs_memory_t *bandlist_allocator)
{
    gs_memory_locked_t *const lmem = (gs_memory_locked_t *)bandlist_allocator;
    gs_memory_t *data_mem = gs_memory_locked_target(lmem);

    gs_memory_free_all(bandlist_allocator,
		       FREE_ALL_STRUCTURES | FREE_ALL_ALLOCATOR,
		       "free_bandlist_memory(locked allocator)");
    if (data_mem)
	gs_memory_free_all(data_mem, FREE_ALL_EVERYTHING,
			   "free_bandlist_memory(data allocator)");
}

/* Create an allocator with a fixed memory limit. */
private int
alloc_render_memory(gs_memory_t **final_allocator,
		    gs_memory_t *base_allocator, long space)
{
    gs_ref_memory_t *rmem =
	ialloc_alloc_state((gs_raw_memory_t *)base_allocator, space);
    vm_spaces spaces;
    int i, code;

    if (rmem == 0)
	return_error(gs_error_VMerror);
    code = ialloc_add_chunk(rmem, space, "alloc_render_memory");
    if (code < 0) {
	gs_memory_free_all((gs_memory_t *)rmem, FREE_ALL_EVERYTHING,
			   "alloc_render_memory");
	return code;
    }
    *final_allocator = (gs_memory_t *)rmem;

    /* Call the reclaim procedure to delete the string marking tables	*/
    /* Only need this once since no other chunks will ever exist	*/

    for ( i = 0; i < countof(spaces_indexed); ++i )
	spaces_indexed[i] = 0;
    space_local = space_global = (gs_ref_memory_t *)rmem;
    spaces.vm_reclaim = gs_nogc_reclaim;	/* no real GC on this chunk */
    GS_RECLAIM(&spaces, false);

    return 0;
}

/* Free an allocator with a fixed memory limit. */
private void
free_render_memory(gs_memory_t *render_allocator)
{
    if (render_allocator)
	gs_memory_free_all(render_allocator, FREE_ALL_EVERYTHING,
			   "free_render_memory");
}
