485 lines
12 KiB
C++
485 lines
12 KiB
C++
// Copyright 2004 David Hilvert <dhilvert@auricle.dyndns.org>,
|
|
// <dhilvert@ugcs.caltech.edu>
|
|
|
|
/* This file is part of the Anti-Lamenessing Engine.
|
|
|
|
The Anti-Lamenessing Engine 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 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
The Anti-Lamenessing Engine 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 the Anti-Lamenessing Engine; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#ifndef __ui_tty_h__
|
|
#define __ui_tty_h__
|
|
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#if HAVE_TIME_H
|
|
#include <time.h>
|
|
#endif
|
|
#if HAVE_SYS_TIME_H
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
#include "../d2.h"
|
|
#include "ui.h"
|
|
#include "util.h"
|
|
|
|
/*
|
|
* TTY user interface
|
|
*/
|
|
|
|
class ui_tty : public ui {
|
|
private:
|
|
int terminal_width;
|
|
char *buffer;
|
|
int buffer_index;
|
|
int status_index;
|
|
int dirty;
|
|
|
|
#ifdef USE_PTHREAD
|
|
pthread_mutex_t lock;
|
|
pthread_t update_thread;
|
|
#endif
|
|
|
|
void clear_buffer() {
|
|
buffer[0] = '\0';
|
|
buffer_index = 0;
|
|
}
|
|
|
|
void write_buffer() {
|
|
if (buffer_index < 0)
|
|
return;
|
|
fputc('\r', ui_stream);
|
|
fputs(buffer, ui_stream);
|
|
status_index = buffer_index;
|
|
}
|
|
|
|
void status_clear() {
|
|
while (status_index < terminal_width) {
|
|
status_index++;
|
|
fprintf(ui_stream, " ");
|
|
}
|
|
}
|
|
|
|
void line_clear() {
|
|
fputc('\r', ui_stream);
|
|
for (int i = 0; i < terminal_width; i++) {
|
|
fprintf(ui_stream, " ");
|
|
}
|
|
}
|
|
|
|
|
|
int status_printf(int count, ...) {
|
|
if (buffer_index < 0)
|
|
return -1;
|
|
|
|
int n;
|
|
|
|
char *local_buffer = (char *) calloc(terminal_width - status_index, sizeof(char));
|
|
|
|
assert(local_buffer);
|
|
|
|
if (!local_buffer)
|
|
return -1;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
char *format = NULL;
|
|
va_list ap;
|
|
|
|
va_start(ap, count);
|
|
|
|
for (int arg = 0; arg < i + 1; arg++)
|
|
format = va_arg(ap, char *);
|
|
for (int arg = i + 1; arg < count; arg++)
|
|
va_arg(ap, char *);
|
|
|
|
assert (format);
|
|
|
|
n = vsnprintf(local_buffer, terminal_width - status_index, format, ap);
|
|
va_end(ap);
|
|
|
|
if (n < 0 || n > terminal_width - status_index - 1)
|
|
continue;
|
|
|
|
fputs(local_buffer, ui_stream);
|
|
|
|
status_index += n;
|
|
|
|
free(local_buffer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* If we reach this point, then there was no valid string produced.
|
|
*/
|
|
|
|
status_clear();
|
|
|
|
free(local_buffer);
|
|
|
|
/*
|
|
* Failure.
|
|
*/
|
|
|
|
return -1;
|
|
}
|
|
|
|
void pad_align_status() {
|
|
for (int i = 0; i < status.steps - status.steps_completed; i++) {
|
|
status_printf(1, " ");
|
|
}
|
|
}
|
|
void pad_match_status() {
|
|
status_printf(1, " ");
|
|
}
|
|
|
|
int write_match_status() {
|
|
return status_printf(1, format_string_working(), status.match_value);
|
|
}
|
|
|
|
void write_status() {
|
|
|
|
if (status.code == status.UNDEFINED
|
|
|| status.code == status.FRAME_DONE
|
|
|| status.code == status.SET_DONE
|
|
|| status.code == status.IP_STEP_DONE) {
|
|
status_clear();
|
|
return;
|
|
}
|
|
|
|
if (status.code == status.GLOBAL_ALIGN
|
|
|| status.code == status.ALIGN
|
|
|| status.code == status.POSTMATCH
|
|
|| status.code == status.EXPOSURE_PASS_2) {
|
|
pad_align_status();
|
|
if (write_match_status()) {
|
|
clear_buffer();
|
|
fprintf(ui_stream, "\n");
|
|
write_match_status();
|
|
}
|
|
}
|
|
|
|
status_printf(1, " | ");
|
|
|
|
switch (status.code) {
|
|
case status_type::LOAD_FILE:
|
|
status_printf(4, "Loading Image (%s)",
|
|
"Loading Image", "Loading", "load",
|
|
status.cache ? "caching" : "cache is full");
|
|
break;
|
|
case status_type::EXPOSURE_PASS_1:
|
|
status_printf(3, "Registering exposure (first pass)", "Registering exposure", "regexp1");
|
|
break;
|
|
case status_type::LODCLUSTER_CREATE:
|
|
status_printf(3, "Creating LOD cluster, scale %g", "Creating LOD clusters", "lodcluster",
|
|
pow(2, -status.align_lod));
|
|
break;
|
|
case status_type::PREMATCH:
|
|
status_printf(3, "Calculating pre-alignment match", "Calculating match", "prematch");
|
|
break;
|
|
case status_type::MULTI:
|
|
status_printf(3, "Assigning multi-alignment pixels", "Multi-alignment", "multi");
|
|
break;
|
|
case status_type::GLOBAL_ALIGN:
|
|
case status_type::ALIGN:
|
|
status_printf(5, "%s [perturb=%6.3g] [lod=%6.3g] [exp_mult=%6.3g %6.3g %6.3g]",
|
|
"%s [perturb=%6.3g] [lod=%6.3g]",
|
|
"%s [perturb=%6.3g]",
|
|
"%s...",
|
|
"align",
|
|
(status.code == status_type::GLOBAL_ALIGN)
|
|
? "Global alignment"
|
|
: "Aligning",
|
|
status.perturb_size,
|
|
pow(2, -status.align_lod),
|
|
status.exp_multiplier[0],
|
|
status.exp_multiplier[1],
|
|
status.exp_multiplier[2]);
|
|
break;
|
|
case status_type::POSTMATCH:
|
|
status_printf(3, "Calculating post-alignment match", "Calculating match", "postmatch");
|
|
break;
|
|
case status_type::EXPOSURE_PASS_2:
|
|
status_printf(3, "Registering exposure (second pass)", "Registering exposure", "regexp2");
|
|
break;
|
|
case status_type::RENDERA:
|
|
status_printf(3, "Rendering alignment reference image", "Rendering", "render-a");
|
|
break;
|
|
case status_type::RENDERD:
|
|
status_printf(3, "Rendering default chain", "Rendering", "render-d");
|
|
break;
|
|
case status_type::RENDERO:
|
|
status_printf(3, "Rendering chain %d", "Rendering", "render-o%d", status.onum);
|
|
break;
|
|
case status_type::WRITED:
|
|
status_printf(4, "Writing default chain to '%s'",
|
|
"Writing '%s'", "Writing", "write-d", d2::image_rw::output_name());
|
|
break;
|
|
case status_type::WRITEO:
|
|
status_printf(3, "Writing image for chain %d", "Writing", "write-o%d", status.onum);
|
|
break;
|
|
case status_type::IP_RENDER:
|
|
status_printf(3, "Frame '%s'%s",
|
|
"Frame '%s'", "Processing",
|
|
d2::image_rw::name(status.frame_num), status.irani_peleg_stage
|
|
? ((status.irani_peleg_stage == 1) ? " [simulate ]" : " [backproject]")
|
|
: "");
|
|
break;
|
|
case status_type::IP_UPDATE:
|
|
status_printf(3, "Updating approximation", "Updating", "update");
|
|
break;
|
|
case status_type::IP_WRITE:
|
|
status_printf(3, "Writing '%s'", "Writing", "write", d2::image_rw::output_name());
|
|
break;
|
|
case status_type::D3_CONTROL_POINT_SOLVE:
|
|
status_printf(1, "Aligning control points, %g%% done, error=%g",
|
|
log(status.cp_cur_perturb / status.cp_max_perturb)
|
|
/ log(status.cp_min_perturb / status.cp_max_perturb),
|
|
status.cp_cur_error);
|
|
break;
|
|
case status_type::D3_SUBDIVIDING_SPACE:
|
|
status_printf(2, "Subdividing space, frame pair (%u, %u), y=%u, x=%u, spaces=%u",
|
|
"Subdividing space", status.frame_num,
|
|
status.secondary_frame_num, status.y_coordinate, status.x_coordinate,
|
|
status.total_spaces);
|
|
break;
|
|
case status_type::D3_UPDATING_OCCUPANCY:
|
|
status_printf(2, "Updating occupancy, step %u/%u, frame %u, space %u/%u",
|
|
"Updating occupancy", status.steps_completed,
|
|
status.steps, status.frame_num,
|
|
status.space_num, status.total_spaces);
|
|
break;
|
|
case status_type::D3_RENDER:
|
|
if (status.filtering == 0 && status.focusing == 0) {
|
|
status_printf(1, "space %u/%u",
|
|
status.space_num, status.total_spaces);
|
|
} else if (status.filtering == 1 && status.focusing == 0) {
|
|
status_printf(1, "frame %u, y=%u, x=%u",
|
|
status.frame_num, status.y_coordinate, status.x_coordinate);
|
|
} else if (status.filtering == 0 && status.focusing == 1) {
|
|
status_printf(1, "y=%u, x=%u, view=%u", status.y_coordinate, status.x_coordinate,
|
|
status.view_num);
|
|
} else if (status.filtering == 1 && status.focusing == 1) {
|
|
status_printf(1, "view=%u, y=%u, x=%u, frame=%u",
|
|
status.view_num, status.y_coordinate, status.x_coordinate,
|
|
status.frame_num);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
status_clear();
|
|
}
|
|
|
|
void write_all() {
|
|
write_buffer();
|
|
write_status();
|
|
}
|
|
|
|
|
|
void printf(const char *format, ...) {
|
|
#ifdef USE_PTHREAD
|
|
pthread_mutex_lock(&lock);
|
|
#endif
|
|
va_list ap;
|
|
int n = -1;
|
|
|
|
if (buffer_index >= 0 && buffer_index < terminal_width /* && format[strlen(format) - 1] != '\n' */) {
|
|
va_start(ap, format);
|
|
n = vsnprintf(buffer + buffer_index, terminal_width - buffer_index, format, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
if (n >= 0 && n < terminal_width - buffer_index) {
|
|
/*
|
|
* The message fits in the buffer, so update the index
|
|
* and write buffer and status information.
|
|
*/
|
|
|
|
buffer_index += n;
|
|
|
|
if (format[strlen(format) - 1] == '\n') {
|
|
line_clear();
|
|
write_buffer();
|
|
} else
|
|
write_all();
|
|
|
|
} else {
|
|
/*
|
|
* The message does not fit in the buffer, so write any
|
|
* existing buffer and append the new text to the stream.
|
|
*/
|
|
if (buffer_index >= 0) {
|
|
assert(buffer_index < terminal_width);
|
|
buffer[buffer_index] = '\0';
|
|
write_buffer();
|
|
}
|
|
buffer_index = -1;
|
|
va_start(ap, format);
|
|
vfprintf(ui_stream, format, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/*
|
|
* This is not the only case that produces a newline,
|
|
* but ignoring other cases should be safe.
|
|
*/
|
|
if (format[strlen(format) - 1] == '\n') {
|
|
buffer_index = 0;
|
|
buffer[0] = '\0';
|
|
}
|
|
#ifdef USE_PTHREAD
|
|
pthread_mutex_unlock(&lock);
|
|
#endif
|
|
}
|
|
|
|
void update() {
|
|
static time_t last_update = 0;
|
|
time_t now = time(NULL);
|
|
|
|
/*
|
|
* Handle DONE status.
|
|
*/
|
|
|
|
if (status.code == status_type::FRAME_DONE) {
|
|
printf(".");
|
|
#ifdef USE_PTHREAD
|
|
pthread_mutex_lock(&lock);
|
|
#endif
|
|
fputc('\n', ui_stream);
|
|
buffer_index = 0;
|
|
buffer[0] = '\0';
|
|
#ifdef USE_PTHREAD
|
|
pthread_mutex_unlock(&lock);
|
|
#endif
|
|
} else if (status.code == status_type::SET_DONE) {
|
|
#ifdef USE_PTHREAD
|
|
pthread_mutex_lock(&lock);
|
|
#endif
|
|
fputc('\n', ui_stream);
|
|
buffer_index = 0;
|
|
buffer[0] = '\0';
|
|
#ifdef USE_PTHREAD
|
|
pthread_mutex_unlock(&lock);
|
|
#endif
|
|
} else {
|
|
|
|
/*
|
|
* Handle optional output.
|
|
*/
|
|
|
|
#ifdef USE_PTHREAD
|
|
pthread_mutex_lock(&lock);
|
|
#endif
|
|
if (now == last_update) {
|
|
dirty = 1;
|
|
} else {
|
|
dirty = 0;
|
|
last_update = now;
|
|
write_all();
|
|
}
|
|
#ifdef USE_PTHREAD
|
|
pthread_mutex_unlock(&lock);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
#ifdef USE_PTHREAD
|
|
static void *update_loop(void *vu) {
|
|
ui_tty *u = (ui_tty *) vu;
|
|
|
|
for(;;) {
|
|
#ifdef HAVE_NANOSLEEP
|
|
struct timespec t;
|
|
t.tv_sec = 0;
|
|
t.tv_nsec = 100000000;
|
|
nanosleep(&t, NULL);
|
|
#else
|
|
sleep(1);
|
|
#endif
|
|
if (u->dirty) {
|
|
pthread_mutex_lock(&u->lock);
|
|
u->write_all();
|
|
u->dirty = 0;
|
|
pthread_mutex_unlock(&u->lock);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Constructor may throw an exception to signal that using ui_wo would
|
|
* be more appropriate.
|
|
*/
|
|
ui_tty() {
|
|
int exception_value = 1;
|
|
|
|
if (!isatty(fileno(ui_stream)))
|
|
throw exception_value;
|
|
|
|
/*
|
|
* Don't use the last column, as this may cause
|
|
* wrapping in some environments (BSD, Hurd).
|
|
*/
|
|
|
|
terminal_width = get_terminal_width(ui_stream) - 1;
|
|
|
|
if (terminal_width < 0)
|
|
throw exception_value;
|
|
|
|
buffer = (char *) calloc(terminal_width + 1, sizeof(char));
|
|
|
|
assert (buffer);
|
|
|
|
if (!buffer)
|
|
throw exception_value;
|
|
|
|
buffer[0] = '\0';
|
|
buffer_index = 0;
|
|
|
|
dirty = 0;
|
|
|
|
/*
|
|
* Start an updating thread if possible.
|
|
*/
|
|
#ifdef USE_PTHREAD
|
|
pthread_mutex_init(&lock, NULL);
|
|
pthread_attr_t pattr;
|
|
pthread_attr_init(&pattr);
|
|
pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_JOINABLE);
|
|
pthread_create(&update_thread, NULL, update_loop, (void *) this);
|
|
#endif
|
|
}
|
|
|
|
~ui_tty() {
|
|
#ifdef USE_PTHREAD
|
|
pthread_cancel(update_thread);
|
|
pthread_join(update_thread, NULL);
|
|
#endif
|
|
free(buffer);
|
|
}
|
|
};
|
|
|
|
#endif
|