1 /*
2 * ngspice-watcher.c
3 *
4 *
5 * Authors:
6 * Michi <st101564@stud.uni-stuttgart.de>
7 *
8 * Web page: https://ahoi.io/project/oregano
9 *
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License as
13 * published by the Free Software Foundation; either version 2 of the
14 * License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public
22 * License along with this program; if not, write to the
23 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
24 * Boston, MA 02110-1301, USA.
25 */
26
27 #include <glib.h>
28 #include <glib/gprintf.h>
29 #include <math.h>
30
31 #include "../tools/thread-pipe.h"
32 #include "ngspice.h"
33 #include "ngspice-analysis.h"
34 #include "../log-interface.h"
35 #include "ngspice-watcher.h"
36
37 enum ERROR_STATE {
38 ERROR_STATE_NO_ERROR,
39 ERROR_STATE_NO_SUCH_FILE_OR_DIRECTORY,
40 ERROR_STATE_ERROR_IN_NETLIST
41 };
42
43 //data wrapper
44 typedef struct {
45 GMutex mutex;
46 GCond cond;
47 gboolean boolean;
48 } IsNgspiceStderrDestroyed;
49
50 //data wrapper
51 typedef struct {
52 gchar *path_to_file;
53 ThreadPipe *pipe;
54 CancelInfo *cancel_info;
55 } NgSpiceSaverResources;
56
57 //data wrapper
58 typedef struct {
59 ThreadPipe *thread_pipe_worker;
60 ThreadPipe *thread_pipe_saver;
61 } NgSpiceWatchForkResources;
62
63 //data wrapper
64 typedef struct {
65 NgSpiceWatchForkResources ngspice_watch_fork_resources;
66 guint cancel_info_count;
67 CancelInfo *cancel_info;
68 } NgSpiceWatchSTDOUTResources;
69
70 //data wrapper
71 typedef struct {
72 ProgressResources *progress_ngspice;
73 LogInterface log;
74 const SimSettings *sim_settings;
75 enum ERROR_STATE *error_state;
76 IsNgspiceStderrDestroyed *is_ngspice_stderr_destroyed;
77 } NgSpiceWatchSTDERRResources;
78
79 //data wrapper
80 typedef struct {
81 GThread *worker;
82 GThread *saver;
83 LogInterface log;
84 const void* emit_instance;
85 GPid *child_pid;
86 gboolean *aborted;
87 guint *num_analysis;
88 GMainLoop *main_loop;
89 gchar *netlist_file;
90 gchar *ngspice_result_file;
91 enum ERROR_STATE *error_state;
92 IsNgspiceStderrDestroyed *is_ngspice_stderr_destroyed;
93 CancelInfo *cancel_info;
94 } NgSpiceWatcherWatchNgSpiceResources;
95
96 /**
97 * Wraps the heavy work of a function into a thread.
98 */
ngspice_worker(NgspiceAnalysisResources * resources)99 static gpointer ngspice_worker (NgspiceAnalysisResources *resources) {
100
101 ngspice_analysis(resources);
102
103 cancel_info_unsubscribe(resources->cancel_info);
104 g_free(resources);
105
106 return NULL;
107 }
108
109 /**
110 * Wraps the heavy work of a function into a thread.
111 */
ngspice_saver(NgSpiceSaverResources * resources)112 static gpointer ngspice_saver (NgSpiceSaverResources *resources)
113 {
114 ngspice_save(resources->path_to_file, resources->pipe, resources->cancel_info);
115
116 cancel_info_unsubscribe(resources->cancel_info);
117 g_free(resources->path_to_file);
118 g_free(resources);
119
120 return NULL;
121 }
122
123 /**
124 * returns the number of strings in a NULL terminated array of strings
125 */
get_count(gchar ** array)126 static int get_count(gchar** array) {
127 int i = 0;
128 while (array[i] != NULL)
129 i++;
130 return i;
131 }
132
133 /**
134 * adds the line number followed by a colon and a space at the beginning of each line
135 */
add_line_numbers(gchar ** string)136 static void add_line_numbers(gchar **string) {
137 gchar **splitted = g_regex_split_simple("\\n", *string, 0, 0);
138 GString *new_string = g_string_new("");
139 //why -1? Because g_regex_split_simple adds one empty string too much at the end
140 //of the array.
141 int count = get_count(splitted) - 1;
142 int max_length = floor(log10((double) count)) + 1;
143 //splitted[i+1] != NULL (why not only i but i+1?) because g_regex_split_simple
144 //adds one empty string too much at the end of the array
145 for (int i = 0; splitted[i+1] != NULL; i++)
146 g_string_append_printf(new_string, "%0*d: %s\n", max_length, i+1, splitted[i]);
147 //remove the last newline, which was added additionally
148 new_string = g_string_truncate(new_string, new_string->len - 1);
149 g_free(*string);
150 *string = new_string->str;
151 g_string_free(new_string, FALSE);
152 }
153
154 //data wrapper
155 typedef struct {
156 const void* emit_instance;
157 gchar *signal_name;
158 } NgspiceEmitData;
159
160 /**
161 * Use this function to return the program main control flow to the
162 * main thread (which is the gui thread).
163 */
g_signal_emit_by_name_main_thread(NgspiceEmitData * data)164 static gboolean g_signal_emit_by_name_main_thread(NgspiceEmitData *data) {
165 const void* emit_instance = data->emit_instance;
166 gchar *signal_name = data->signal_name;
167 g_free(data);
168 g_signal_emit_by_name (G_OBJECT (emit_instance), signal_name);
169 g_free(signal_name);
170 return G_SOURCE_REMOVE;
171 }
172
173 /**
174 * main function of the ngspice watcher
175 */
ngspice_watcher_main(GMainLoop * main_loop)176 static gpointer ngspice_watcher_main(GMainLoop *main_loop) {
177 g_main_loop_run(main_loop);
178
179 // unrefs its GMainContext by 1
180 g_main_loop_unref(main_loop);
181
182 return NULL;
183 }
184
185 /**
186 * forks data to file and heap
187 */
ngspice_watcher_fork_data(NgSpiceWatchForkResources * resources,gpointer data,gsize size)188 static void ngspice_watcher_fork_data(NgSpiceWatchForkResources *resources, gpointer data, gsize size) {
189 thread_pipe_push(resources->thread_pipe_worker, data, size);
190 /**
191 * size_in = size - 1, because the trailing 0 of the string should not
192 * be written to file.
193 */
194 thread_pipe_push(resources->thread_pipe_saver, data, size - 1);
195 }
196
197 /**
198 * forks eof to file-pipe and heap-pipe
199 */
ngspice_watcher_fork_eof(NgSpiceWatchForkResources * resources)200 static void ngspice_watcher_fork_eof(NgSpiceWatchForkResources *resources) {
201 thread_pipe_set_write_eof(resources->thread_pipe_worker);
202 thread_pipe_set_write_eof(resources->thread_pipe_saver);
203 }
204
205 /**
206 * Does not handle input resources.
207 */
ngspice_watcher_watch_stdout_resources(GIOChannel * channel,GIOCondition condition,NgSpiceWatchSTDOUTResources * resources)208 static gboolean ngspice_watcher_watch_stdout_resources(GIOChannel *channel, GIOCondition condition, NgSpiceWatchSTDOUTResources *resources) {
209 gchar *str_return = NULL;
210 gsize length;
211 gsize terminator_pos;
212 GError *error = NULL;
213
214 resources->cancel_info_count++;
215 if (resources->cancel_info_count % 50 == 0 && cancel_info_is_cancel(resources->cancel_info)) {
216 return G_SOURCE_REMOVE;
217 }
218
219 GIOStatus status = g_io_channel_read_line(channel, &str_return, &length, &terminator_pos, &error);
220 if (error) {
221 gchar *message = g_strdup_printf ("spice pipe stdout: %s - %i", error->message, error->code);
222 g_printf("%s", message);
223 g_free(message);
224 g_clear_error (&error);
225 } else if (status == G_IO_STATUS_NORMAL && length > 0) {
226 ngspice_watcher_fork_data(&resources->ngspice_watch_fork_resources, str_return, length + 1);
227 } else if (status == G_IO_STATUS_EOF) {
228 return G_SOURCE_REMOVE;
229 }
230 if (str_return)
231 g_free(str_return);
232 return G_SOURCE_CONTINUE;
233 }
234
235 /**
236 * ngspice reading source function
237 *
238 * reads the pipe (stdout) of ngspice
239 */
ngspice_watcher_watch_stdout(GIOChannel * channel,GIOCondition condition,NgSpiceWatchSTDOUTResources * resources)240 static gboolean ngspice_watcher_watch_stdout(GIOChannel *channel, GIOCondition condition, NgSpiceWatchSTDOUTResources *resources) {
241
242 gboolean g_source_continue = ngspice_watcher_watch_stdout_resources(channel, condition, resources);
243
244 if (g_source_continue == G_SOURCE_CONTINUE)
245 return G_SOURCE_CONTINUE;
246
247 ngspice_watcher_fork_eof(&resources->ngspice_watch_fork_resources);
248 g_source_destroy(g_main_current_source());
249 cancel_info_unsubscribe(resources->cancel_info);
250 g_free(resources);
251 return G_SOURCE_REMOVE;
252 }
253
ngspice_watch_ngspice_resources_finalize(NgSpiceWatcherWatchNgSpiceResources * resources)254 static void ngspice_watch_ngspice_resources_finalize(NgSpiceWatcherWatchNgSpiceResources *resources) {
255 g_source_destroy(g_main_current_source());
256 g_main_loop_quit(resources->main_loop);
257 g_spawn_close_pid (*resources->child_pid);
258 *resources->child_pid = 0;
259
260 g_free(resources->ngspice_result_file);
261 g_free(resources->netlist_file);
262 g_free(resources->error_state);
263 g_mutex_clear(&resources->is_ngspice_stderr_destroyed->mutex);
264 g_cond_clear(&resources->is_ngspice_stderr_destroyed->cond);
265 g_free(resources);
266 }
267
print_additional_info(LogInterface log,const gchar * ngspice_result_file,const gchar * netlist_file)268 static void print_additional_info(LogInterface log, const gchar *ngspice_result_file, const gchar *netlist_file) {
269 log.log_append_error(log.log, "\n### spice output: ###\n\n");
270 gchar *ngspice_error_contents = NULL;
271 gsize ngspice_error_length;
272 GError *ngspice_error_read_error = NULL;
273
274 g_file_get_contents(ngspice_result_file, &ngspice_error_contents, &ngspice_error_length, &ngspice_error_read_error);
275 add_line_numbers(&ngspice_error_contents);
276 log.log_append_error(log.log, ngspice_error_contents);
277 g_free(ngspice_error_contents);
278 if (ngspice_error_read_error != NULL)
279 g_error_free(ngspice_error_read_error);
280
281 gchar *netlist_contents = NULL;
282 gsize netlist_lentgh;
283 GError *netlist_read_error = NULL;
284 g_file_get_contents(netlist_file, &netlist_contents, &netlist_lentgh, &netlist_read_error);
285 add_line_numbers(&netlist_contents);
286 log.log_append_error(log.log, "\n\n### netlist: ###\n\n");
287 log.log_append_error(log.log, netlist_contents);
288 g_free(netlist_contents);
289 if (netlist_read_error != NULL)
290 g_error_free(netlist_read_error);
291 }
292
293 enum NGSPICE_WATCHER_RETURN_VALUE {
294 NGSPICE_WATCHER_RETURN_VALUE_DONE,
295 NGSPICE_WATCHER_RETURN_VALUE_ABORTED,
296 NGSPICE_WATCHER_RETURN_VALUE_CANCELED
297 };
298
299 /**
300 * Does not care about input resource handling.
301 */
302 static enum NGSPICE_WATCHER_RETURN_VALUE
ngspice_watcher_watch_ngspice_resources(GPid pid,gint status,NgSpiceWatcherWatchNgSpiceResources * resources)303 ngspice_watcher_watch_ngspice_resources (GPid pid, gint status, NgSpiceWatcherWatchNgSpiceResources *resources) {
304 GThread *worker = resources->worker;
305 GThread *saver = resources->saver;
306 LogInterface log = resources->log;
307 guint *num_analysis = resources->num_analysis;
308 enum ERROR_STATE *error_state = resources->error_state;
309 IsNgspiceStderrDestroyed *is_ngspice_stderr_destroyed = resources->is_ngspice_stderr_destroyed;
310
311 // wait for stderr to finish reading
312 g_mutex_lock(&is_ngspice_stderr_destroyed->mutex);
313 while (!is_ngspice_stderr_destroyed->boolean)
314 g_cond_wait(&is_ngspice_stderr_destroyed->cond, &is_ngspice_stderr_destroyed->mutex);
315 g_mutex_unlock(&is_ngspice_stderr_destroyed->mutex);
316
317 GError *exit_error = NULL;
318 gboolean exited_normal = g_spawn_check_exit_status(status, &exit_error);
319 if (exit_error != NULL)
320 g_error_free(exit_error);
321
322 g_thread_join(worker);
323
324 if (cancel_info_is_cancel(resources->cancel_info))
325 return NGSPICE_WATCHER_RETURN_VALUE_CANCELED;
326
327
328 if (!exited_normal) {
329 // check for exit via return in main, exit() or _exit() of the child, see man
330 // waitpid(2)
331 // WIFEXITED(wstatus)
332 // returns true if the child terminated normally, that is, by call‐
333 // ing exit(3) or _exit(2), or by returning from main().
334 if (!(WIFEXITED (status)))
335 log.log_append_error(log.log, "### spice exited with exception ###\n");
336 else
337 log.log_append_error(log.log, "### spice exited abnormally ###\n");
338
339 g_thread_join(saver);
340
341 switch (*error_state) {
342 case ERROR_STATE_NO_ERROR:
343 log.log_append_error(log.log, "### unknown error detected ###\n");
344 log.log_append_error(log.log, "The following information might help you to analyze the error.\n");
345
346 print_additional_info(log, resources->ngspice_result_file, resources->netlist_file);
347 break;
348 case ERROR_STATE_NO_SUCH_FILE_OR_DIRECTORY:
349 log.log_append_error(log.log, "spice could not simulate because netlist generation failed.\n");
350 break;
351 case ERROR_STATE_ERROR_IN_NETLIST:
352 log.log_append_error(log.log, "### netlist error detected ###\n");
353 log.log_append_error(log.log, "You made a mistake in the simulation settings or part properties.\n");
354 log.log_append_error(log.log, "The following information will help you to analyze the error.\n");
355
356 print_additional_info(log, resources->ngspice_result_file, resources->netlist_file);
357 break;
358 }
359
360 return NGSPICE_WATCHER_RETURN_VALUE_ABORTED;
361 }
362 // saver not needed any more. It could have been needed by error handling.
363 g_thread_unref(saver);
364
365 if (*num_analysis == 0) {
366 log.log_append_error(log.log, _("### Too few or none analysis found ###\n"));
367 return NGSPICE_WATCHER_RETURN_VALUE_ABORTED;
368 }
369
370 return NGSPICE_WATCHER_RETURN_VALUE_DONE;
371
372 }
373
374 /**
375 * function is called after ngspice process has died and the ngspice reading
376 * source function is finished with reading to
377 * - clean up,
378 * - check if all went good or fail,
379 * - wait for data conversion thread,
380 * - return the main program flow to the gui thread.
381 */
ngspice_watcher_watch_ngspice(GPid pid,gint status,NgSpiceWatcherWatchNgSpiceResources * resources)382 static void ngspice_watcher_watch_ngspice (GPid pid, gint status, NgSpiceWatcherWatchNgSpiceResources *resources) {
383 enum NGSPICE_WATCHER_RETURN_VALUE ret_val = ngspice_watcher_watch_ngspice_resources (pid, status, resources);
384
385 NgspiceEmitData *emitData = g_malloc(sizeof(NgspiceEmitData));
386 emitData->emit_instance = resources->emit_instance;
387
388 switch(ret_val) {
389 case NGSPICE_WATCHER_RETURN_VALUE_ABORTED:
390 case NGSPICE_WATCHER_RETURN_VALUE_CANCELED:
391 emitData->signal_name = g_strdup("aborted");
392 *resources->aborted = TRUE;
393 break;
394 case NGSPICE_WATCHER_RETURN_VALUE_DONE:
395 emitData->signal_name = g_strdup("done");
396 break;
397 }
398
399 cancel_info_unsubscribe(resources->cancel_info);
400 ngspice_watch_ngspice_resources_finalize(resources);
401
402 /*
403 * return to main thread
404 *
405 * Don't return too early, because if you do, the ngspice
406 * object could be finalized but some resources depend on it.
407 */
408 g_main_context_invoke(NULL, (GSourceFunc)g_signal_emit_by_name_main_thread, emitData);
409 }
410
411 /**
412 * Extracts a progress number (time of transient analysis)
413 * out of a string (if existing) and saves it to the thread-shared
414 * progress variable.
415 */
read_progress_ngspice(ProgressResources * progress_ngspice,gdouble progress_end,const gchar * line)416 static void read_progress_ngspice(ProgressResources *progress_ngspice, gdouble progress_end, const gchar *line) {
417 if (!g_regex_match_simple("Reference value.*\\r", line, 0, 0))
418 return;
419 gchar **splitted = g_regex_split_simple(".* (.+)\\r", line, 0, 0);
420 gchar **ptr;
421 for (ptr = splitted; *ptr != NULL; ptr++)
422 if (**ptr != 0)
423 break;
424 if (*ptr != NULL) {
425 gdouble progress_absolute = g_ascii_strtod(*ptr, NULL);
426
427 g_mutex_lock(&progress_ngspice->progress_mutex);
428 progress_ngspice->progress = progress_absolute / progress_end;
429 if (g_str_has_suffix(line, "\r\n"))
430 progress_ngspice->progress = 1;
431 progress_ngspice->time = g_get_monotonic_time();
432 g_mutex_unlock(&progress_ngspice->progress_mutex);
433 }
434 g_strfreev(splitted);
435 }
436
437 /**
438 * Reads stderr of ngspice.
439 *
440 * stderr of ngspice might contain progress information.
441 */
ngspice_child_stderr_cb(GIOChannel * channel,GIOCondition condition,NgSpiceWatchSTDERRResources * resources)442 static gboolean ngspice_child_stderr_cb (GIOChannel *channel, GIOCondition condition,
443 NgSpiceWatchSTDERRResources *resources)
444 {
445 LogInterface log = resources->log;
446 const SimSettings* const sim_settings = resources->sim_settings;
447 ProgressResources *progress_ngspice = resources->progress_ngspice;
448 enum ERROR_STATE *error_state = resources->error_state;
449 IsNgspiceStderrDestroyed *is_ngspice_stderr_destroyed = resources->is_ngspice_stderr_destroyed;
450
451 gchar *line = NULL;
452 gsize len, terminator;
453 GError *e = NULL;
454
455 GIOStatus status = g_io_channel_read_line (channel, &line, &len, &terminator, &e);
456 if (e) {
457 gchar *message = g_strdup_printf("spice pipe stderr: %s - %i", e->message, e->code);
458 log.log_append_error(log.log, message);
459 g_free(message);
460 g_clear_error (&e);
461 } else if (status == G_IO_STATUS_NORMAL && len > 0) {
462 log.log_append_error(log.log, line);
463
464 if (g_str_has_suffix(line, ": No such file or directory\n"))
465 *error_state = ERROR_STATE_NO_SUCH_FILE_OR_DIRECTORY;
466 if (g_str_equal(line, "spice stopped due to error, no simulation run!\n"))
467 *error_state = ERROR_STATE_ERROR_IN_NETLIST;
468
469 gdouble progress_end = sim_settings_get_trans_stop(sim_settings);
470 read_progress_ngspice(progress_ngspice, progress_end, line);
471 } else if (status == G_IO_STATUS_EOF) {
472 g_source_destroy(g_main_current_source());
473 g_free(resources);
474
475 // emit signal, that stderr reading has finished
476 g_mutex_lock(&is_ngspice_stderr_destroyed->mutex);
477 is_ngspice_stderr_destroyed->boolean = TRUE;
478 g_cond_signal(&is_ngspice_stderr_destroyed->cond);
479 g_mutex_unlock(&is_ngspice_stderr_destroyed->mutex);
480
481 return G_SOURCE_REMOVE;
482 }
483 if (line)
484 g_free (line);
485
486 return G_SOURCE_CONTINUE;
487 }
488
489 /**
490 * @resources: caller frees
491 *
492 * Prepares data structures to launch some threads and finally launches them.
493 *
494 * The launched threads are:
495 * - process ngspice
496 * - thread watcher
497 * - thread saver
498 * - thread worker
499 *
500 * As you should know ngspice is the program that actually simulates the simulation.
501 *
502 * The watcher thread handles stdout- and death-events of the ngspice process.
503 * stdout data is forked to the threads "saver" and "worker".
504 * As response to the death-event of ngspice, the watcher
505 * - cleans the field of war,
506 * - checks if all went good and creates error messages if not all went good,
507 * - waits for the worker to finish work,
508 * - finally returns the main program flow to the gui thread.
509 *
510 * The stderr-events are handled by the main (gui) thread, because it is not heavy work.
511 * Furthermore additionally shared variables can be avoided.
512 *
513 * The saver saves the data to SSD/HDD (temporary folder). It is needed to create
514 * good error messages in case of failure. Besides of that the user can analyze
515 * the data with external/other programs.
516 *
517 * The worker parses the stream of data, interprets and converts it to structured data
518 * so it can be plotted by gui.
519 */
ngspice_watcher_build_and_launch(const NgspiceWatcherBuildAndLaunchResources * resources)520 void ngspice_watcher_build_and_launch(const NgspiceWatcherBuildAndLaunchResources *resources) {
521 LogInterface log = resources->log;
522 const SimSettings* const sim_settings = resources->sim_settings;
523 gboolean is_vanilla = resources->is_vanilla;
524 GPid *child_pid = resources->child_pid;
525 gboolean *aborted = resources->aborted;
526 const void* emit_instance = resources->emit_instance;
527 guint *num_analysis = resources->num_analysis;
528 ProgressResources *progress_ngspice = resources->progress_ngspice;
529 ProgressResources *progress_reader = resources->progress_reader;
530 GList **analysis = resources->analysis;
531 AnalysisTypeShared *current = resources->current;
532 GError *e = NULL;
533
534 char *argv[] = {NULL, "-b", resources->netlist_file, NULL};
535
536 if (is_vanilla)
537 argv[0] = SPICE_EXE;
538 else
539 argv[0] = NGSPICE_EXE;
540
541 gint ngspice_stdout_fd;
542 gint ngspice_stderr_fd;
543 // Launch ngspice
544 if (!g_spawn_async_with_pipes (NULL, // Working directory
545 argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, NULL,
546 NULL, child_pid,
547 NULL, // STDIN
548 &ngspice_stdout_fd, // STDOUT
549 &ngspice_stderr_fd, // STDERR
550 &e)) {
551
552 *aborted = TRUE;
553 log.log_append_error(log.log, _("Unable to execute NgSpice."));
554 g_signal_emit_by_name (G_OBJECT (emit_instance), "aborted");
555 g_clear_error (&e);
556 return;
557
558 }
559
560 // synchronizes stderr listener with is_ngspice_finished listener (needed for error handling)
561 IsNgspiceStderrDestroyed *is_ngspice_stderr_destroyed = g_new0(IsNgspiceStderrDestroyed, 1);
562 g_mutex_init(&is_ngspice_stderr_destroyed->mutex);
563 g_cond_init(&is_ngspice_stderr_destroyed->cond);
564 is_ngspice_stderr_destroyed->boolean = FALSE;
565
566 // variable needed for error handling
567 enum ERROR_STATE *error_state = g_new0(enum ERROR_STATE, 1);
568
569 GMainContext *forker_context = g_main_context_new();
570 GMainLoop *forker_main_loop = g_main_loop_new(forker_context, FALSE);
571 g_main_context_unref(forker_context);
572
573 // Create pipes to fork the stdout data of ngspice
574 ThreadPipe *thread_pipe_worker = thread_pipe_new(20, 2048);
575 ThreadPipe *thread_pipe_saver = thread_pipe_new(20, 2048);
576
577 /**
578 * Launch analyzer
579 */
580 NgspiceAnalysisResources *ngspice_worker_resources = g_new0(NgspiceAnalysisResources, 1);
581 ngspice_worker_resources->analysis = analysis;
582 ngspice_worker_resources->buf = NULL;
583 ngspice_worker_resources->is_vanilla = is_vanilla;
584 ngspice_worker_resources->current = current;
585 ngspice_worker_resources->no_of_data_rows_ac = 0;
586 ngspice_worker_resources->no_of_data_rows_dc = 0;
587 ngspice_worker_resources->no_of_data_rows_op = 0;
588 ngspice_worker_resources->no_of_data_rows_transient = 0;
589 ngspice_worker_resources->no_of_data_rows_noise = 0;
590 ngspice_worker_resources->no_of_variables = 0;
591 ngspice_worker_resources->num_analysis = num_analysis;
592 ngspice_worker_resources->pipe = thread_pipe_worker;
593 ngspice_worker_resources->progress_reader = progress_reader;
594 ngspice_worker_resources->sim_settings = sim_settings;
595 ngspice_worker_resources->cancel_info = resources->cancel_info;
596 cancel_info_subscribe(ngspice_worker_resources->cancel_info);
597
598 GThread *worker = g_thread_new("spice worker", (GThreadFunc)ngspice_worker, ngspice_worker_resources);
599
600 /**
601 * Launch output saver
602 */
603 NgSpiceSaverResources *ngspice_saver_resources = g_new0(NgSpiceSaverResources, 1);
604 ngspice_saver_resources->path_to_file = g_strdup(resources->ngspice_result_file);
605 ngspice_saver_resources->pipe = thread_pipe_saver;
606 ngspice_saver_resources->cancel_info = resources->cancel_info;
607 cancel_info_subscribe(ngspice_saver_resources->cancel_info);
608
609 GThread *saver = g_thread_new("spice saver", (GThreadFunc)ngspice_saver, ngspice_saver_resources);
610
611 /**
612 * Add an ngspice-is-finished watcher
613 */
614 NgSpiceWatcherWatchNgSpiceResources *ngspice_watcher_watch_ngspice_resources = g_new0(NgSpiceWatcherWatchNgSpiceResources, 1);
615 ngspice_watcher_watch_ngspice_resources->emit_instance = emit_instance;
616 ngspice_watcher_watch_ngspice_resources->aborted = aborted;
617 ngspice_watcher_watch_ngspice_resources->child_pid = child_pid;
618 ngspice_watcher_watch_ngspice_resources->log = log;
619 ngspice_watcher_watch_ngspice_resources->num_analysis = num_analysis;
620 ngspice_watcher_watch_ngspice_resources->worker = worker;
621 ngspice_watcher_watch_ngspice_resources->saver = saver;
622 ngspice_watcher_watch_ngspice_resources->main_loop = forker_main_loop;
623 ngspice_watcher_watch_ngspice_resources->ngspice_result_file = g_strdup(resources->ngspice_result_file);
624 ngspice_watcher_watch_ngspice_resources->netlist_file = g_strdup(resources->netlist_file);
625 ngspice_watcher_watch_ngspice_resources->error_state = error_state;
626 ngspice_watcher_watch_ngspice_resources->is_ngspice_stderr_destroyed = is_ngspice_stderr_destroyed;
627 ngspice_watcher_watch_ngspice_resources->cancel_info = resources->cancel_info;
628 cancel_info_subscribe(ngspice_watcher_watch_ngspice_resources->cancel_info);
629
630 GSource *child_watch_source = g_child_watch_source_new (*child_pid);
631 g_source_set_priority (child_watch_source, G_PRIORITY_LOW);
632 g_source_set_callback (child_watch_source, (GSourceFunc)ngspice_watcher_watch_ngspice, ngspice_watcher_watch_ngspice_resources, NULL);
633 g_source_attach (child_watch_source, forker_context);
634 g_source_unref (child_watch_source);
635
636 /**
637 * Add a GIOChannel to read from process stdout
638 */
639 NgSpiceWatchSTDOUTResources *ngspice_watch_stdout_resources = g_new0(NgSpiceWatchSTDOUTResources, 1);
640 ngspice_watch_stdout_resources->ngspice_watch_fork_resources.thread_pipe_worker = thread_pipe_worker;
641 ngspice_watch_stdout_resources->ngspice_watch_fork_resources.thread_pipe_saver = thread_pipe_saver;
642 ngspice_watch_stdout_resources->cancel_info = resources->cancel_info;
643 cancel_info_subscribe(ngspice_watch_stdout_resources->cancel_info);
644
645 GIOChannel *ngspice_stdout_channel = g_io_channel_unix_new(ngspice_stdout_fd);
646 g_io_channel_set_close_on_unref(ngspice_stdout_channel, TRUE);
647 GSource *ngspice_stdout_source = g_io_create_watch (ngspice_stdout_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | G_IO_NVAL);
648 g_io_channel_unref(ngspice_stdout_channel);
649 g_source_set_priority (ngspice_stdout_source, G_PRIORITY_HIGH);
650 g_source_set_callback (ngspice_stdout_source, (GSourceFunc)ngspice_watcher_watch_stdout, ngspice_watch_stdout_resources, NULL);
651 g_source_attach (ngspice_stdout_source, forker_context);
652 g_source_unref (ngspice_stdout_source);
653
654 /**
655 * Add a GIOChannel to read from process stderr (attach to gui thread because it prints to log).
656 * I hope that ngspice does not print too much errors so that it is a minor work
657 * that does not hold the gui back from paint and user events
658 */
659 NgSpiceWatchSTDERRResources *ngspice_watch_stderr_resources = g_new0(NgSpiceWatchSTDERRResources, 1);
660 ngspice_watch_stderr_resources->log = log;
661 ngspice_watch_stderr_resources->sim_settings = sim_settings;
662 ngspice_watch_stderr_resources->progress_ngspice = progress_ngspice;
663 ngspice_watch_stderr_resources->error_state = error_state;
664 ngspice_watch_stderr_resources->is_ngspice_stderr_destroyed = is_ngspice_stderr_destroyed;
665
666 GIOChannel *ngspice_stderr_channel = g_io_channel_unix_new (ngspice_stderr_fd);
667 g_io_channel_set_close_on_unref(ngspice_stderr_channel, TRUE);
668 // sometimes there is no data and then the GUI will hang up if NONBLOCK not set
669 g_io_channel_set_flags(ngspice_stderr_channel, g_io_channel_get_flags(ngspice_stderr_channel) | G_IO_FLAG_NONBLOCK, NULL);
670 GSource *channel_stderr_watch_source = g_io_create_watch(ngspice_stderr_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | G_IO_NVAL);
671 g_io_channel_unref(ngspice_stderr_channel);
672 g_source_set_priority (channel_stderr_watch_source, G_PRIORITY_LOW);
673 g_source_set_callback (channel_stderr_watch_source, (GSourceFunc)ngspice_child_stderr_cb, ngspice_watch_stderr_resources, NULL);
674 g_source_attach (channel_stderr_watch_source, NULL);
675 g_source_unref (channel_stderr_watch_source);
676
677 // Launch watcher
678 g_thread_unref(g_thread_new("spice forker", (GThreadFunc)ngspice_watcher_main, forker_main_loop));
679 }
680
ngspice_watcher_build_and_launch_resources_new(OreganoNgSpice * ngspice)681 NgspiceWatcherBuildAndLaunchResources *ngspice_watcher_build_and_launch_resources_new(OreganoNgSpice *ngspice) {
682
683 NgspiceWatcherBuildAndLaunchResources *resources = g_new0(NgspiceWatcherBuildAndLaunchResources, 1);
684
685 resources->is_vanilla = ngspice->priv->is_vanilla;
686 resources->aborted = &ngspice->priv->aborted;
687 resources->analysis = &ngspice->priv->analysis;
688 resources->child_pid = &ngspice->priv->child_pid;
689 resources->current = &ngspice->priv->current;
690 resources->emit_instance = ngspice;
691
692 resources->log.log = ngspice->priv->schematic;
693 resources->log.log_append = (LogFunction)schematic_log_append;
694 resources->log.log_append_error = (LogFunction)schematic_log_append_error;
695
696 resources->num_analysis = &ngspice->priv->num_analysis;
697 resources->progress_ngspice = &ngspice->priv->progress_ngspice;
698 resources->progress_reader = &ngspice->priv->progress_reader;
699 resources->sim_settings = schematic_get_sim_settings(ngspice->priv->schematic);
700
701 resources->netlist_file = g_strdup("/tmp/netlist.tmp");
702 resources->ngspice_result_file = g_strdup("/tmp/netlist.lst");
703
704 resources->cancel_info = ngspice->priv->cancel_info;
705 cancel_info_subscribe(resources->cancel_info);
706
707 return resources;
708 }
709
ngspice_watcher_build_and_launch_resources_finalize(NgspiceWatcherBuildAndLaunchResources * resources)710 void ngspice_watcher_build_and_launch_resources_finalize(NgspiceWatcherBuildAndLaunchResources *resources) {
711 cancel_info_unsubscribe(resources->cancel_info);
712 g_free(resources->netlist_file);
713 g_free(resources->ngspice_result_file);
714 g_free(resources);
715 }
716