1 #include <pthread.h>
2 #include "demo.h"
3 
4 // the only dynamic information which needs be shared between the two threads
5 // is the last polyfill origin, the total filled count, and whose turn it is.
6 
7 struct marsh {
8   int id;                 // unique for each thread, mono-increasing
9   int* turn;              // whose turn it is to prep the ncvisual, shared
10   int* rturn;             // whose turn it is to render, shared
11   int* polyy;             // last shared polyfill origin (y)
12   int* polyx;             // last shared polyfill origin (x)
13   int maxy;               // pixels, y dimension
14   int maxx;               // pixels, x dimension
15   uint32_t* polypixel;    // last shared polyfill pixel (rgba)
16   int* filled;            // shared, how many have we filled?
17   unsigned* done;         // shared, are we done?
18   struct ncvisual* ncv;   // our copy of the ncv
19   struct ncplane* label;  // single, shared between threads
20   struct notcurses* nc;
21   struct ncvisual_options vopts; // each has their own copy
22   struct timespec tspec;  // delay param, copy per thread
23 };
24 
25 // guard turn
26 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
27 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
28 
29 // guard rturn
30 static pthread_mutex_t rlock = PTHREAD_MUTEX_INITIALIZER;
31 static pthread_cond_t rcond = PTHREAD_COND_INITIALIZER;
32 
33 static int
display(struct marsh * m,int filledcopy,long threshold_painted)34 display(struct marsh* m, int filledcopy, long threshold_painted){
35   ncplane_printf_aligned(m->label, 0, NCALIGN_CENTER, "Yield: %3.1f%%", ((double)filledcopy * 100) / threshold_painted);
36   ncplane_reparent(m->vopts.n, m->label);
37   ncplane_move_below(m->vopts.n, m->label);
38   if(ncvisual_blit(m->nc, m->ncv, &m->vopts) == NULL){
39     return -1;
40   }
41   if(demo_render(m->nc)){
42     return -1;
43   }
44   *m->rturn = !*m->rturn;
45   ncplane_reparent(m->vopts.n, m->vopts.n);
46   pthread_mutex_unlock(&rlock);
47   pthread_cond_signal(&rcond);
48   pthread_mutex_lock(&lock);
49   while(*m->turn != m->id && !*m->done){
50     pthread_cond_wait(&cond, &lock);
51   }
52   return 0;
53 }
54 
55 static int
yielder(struct marsh * m)56 yielder(struct marsh* m){
57 #define MAXITER (1024 / 2)
58   int iters = 0;
59   // less than this, and we exit almost immediately. more than this, and we
60   // run closer to twenty seconds. 11/50 it is, then. pixels are different.
61   const long threshold_painted = m->maxy * m->maxx * 11 / 50;
62   if(m->id == 1){
63     pthread_mutex_lock(&rlock);
64     if(display(m, 0, threshold_painted)){
65       *m->done = 1;
66       pthread_mutex_unlock(&rlock);
67       return -1;
68     }
69   }else{
70     pthread_mutex_lock(&lock);
71     while(*m->turn != m->id && !*m->done){
72       pthread_cond_wait(&cond, &lock);
73     }
74   }
75   while(!*m->done && *m->filled < threshold_painted && iters < MAXITER){
76     int pfilled = 0;
77     // the first time the first thread runs, it does not pick up the previous
78     // polyfill origin (as there was none). all other runs, we do. fill in our
79     // own copy with the other thread's move.
80     if(iters || m->id){
81       ncvisual_polyfill_yx(m->ncv, *m->polyy, *m->polyx, *m->polypixel);
82     }
83     int filledcopy;
84     do{
85       ++iters;
86       int x = rand() % m->maxx;
87       int y = rand() % m->maxy;
88       ncvisual_at_yx(m->ncv, y, x, m->polypixel);
89       if(ncpixel_a(*m->polypixel) != 0xff){ // don't do areas we've already done
90         continue;
91       }
92       if(ncpixel_g(*m->polypixel) < 0x80){ // only do land, which is whiter than blue
93         continue;
94       }
95       ncpixel_set_a(m->polypixel, 0xfe);
96       ncpixel_set_rgb8(m->polypixel, (rand() % 128) + 128, 0, ncpixel_b(*m->polypixel) / 4);
97       pfilled = ncvisual_polyfill_yx(m->ncv, y, x, *m->polypixel);
98       if(pfilled < 0){
99         break;
100       }else if(pfilled){
101         *m->polyy = y;
102         *m->polyx = x;
103       }
104     }while(pfilled == 0);
105     if(pfilled < 0){
106       break;
107     }
108     *m->turn = !*m->turn;
109     *m->filled += pfilled;
110     if(*m->filled > threshold_painted){
111       *m->filled = threshold_painted; // don't allow printing of 100.1% etc
112     }
113     filledcopy = *m->filled;
114     pthread_mutex_unlock(&lock);
115     pthread_cond_signal(&cond);
116 
117     pthread_mutex_lock(&rlock);
118     while(*m->rturn != m->id && !*m->done){
119       pthread_cond_wait(&rcond, &rlock);
120     }
121     if(display(m, filledcopy, threshold_painted)){
122       pthread_mutex_unlock(&rlock);
123       pthread_mutex_lock(&lock);
124       break;
125     }
126   }
127   *m->done = 1;
128   pthread_mutex_unlock(&lock);
129   pthread_cond_signal(&cond);
130   pthread_cond_signal(&rcond);
131   return 0;
132 #undef MAXITER
133 }
134 
135 static void*
yielder_thread(void * vmarsh)136 yielder_thread(void* vmarsh){
137   struct marsh* m = vmarsh;
138   if(yielder(m)){
139     return NULL;
140   }
141   return NULL; // FIXME indicate failure/success
142 }
143 
yield_demo(struct notcurses * nc,uint64_t startns)144 int yield_demo(struct notcurses* nc, uint64_t startns){
145   (void)startns;
146   if(!notcurses_canopen_images(nc)){
147     return 0;
148   }
149   unsigned dimy, dimx;
150   struct ncplane* std = notcurses_stddim_yx(nc, &dimy, &dimx);
151   // in sixel-based implementation, if we redraw each cycle, the underlying
152   // material will be redrawn, taking time. erasing won't eliminate the
153   // flicker, but it does minimize it.
154   ncplane_erase(std);
155   char* pic = find_data("worldmap.png");
156   struct ncvisual* v1 = ncvisual_from_file(pic);
157   struct ncvisual* v2 = ncvisual_from_file(pic);
158   free(pic);
159   if(v1 == NULL || v2 == NULL){
160     ncvisual_destroy(v1);
161     ncvisual_destroy(v2);
162     return -1;
163   }
164   // can we do bitmaps?
165   const bool bitmaps = notcurses_canpixel(nc);
166   struct ncplane_options nopts = {
167     .y = 1,
168     // this chops one line off the bottom that we could use in the case
169     // of kitty graphics (xterm can't draw on the bottom row without
170     // scrolling). this doesn't hit clamping thresholds since we're
171     // starting on row 1. what we really need is a valid story for trimming
172     // sprixels which cross the bottom row, see #2195.
173     .rows = dimy - 1 - bitmaps, // FIXME
174     .cols = dimx,
175     .name = "wmap",
176   };
177   struct ncplane_options labopts = {
178     .y = 3,
179     .x = NCALIGN_CENTER,
180     .rows = 1,
181     .cols = 13,
182     .name = "pcnt",
183     .flags = NCPLANE_OPTION_HORALIGNED,
184   };
185   struct ncplane* label = ncplane_create(std, &labopts);
186   if(label == NULL){
187     ncvisual_destroy(v1);
188     ncvisual_destroy(v2);
189     return -1;
190   }
191   int turn = 0;
192   int rturn = 1; // second thread draws the unmodified map first
193   unsigned done = 0;
194   int filled = 0;
195   uint32_t polypixel = 0;
196   int polyx = 0;
197   int polyy = 0;
198   struct marsh m1 = {
199     .id = 0,
200     .label = label,
201     .nc = nc,
202     .ncv = v1,
203     .done = &done,
204     .polyy = &polyy,
205     .polyx = &polyx,
206     .polypixel = &polypixel,
207     .turn = &turn,
208     .filled = &filled,
209     .rturn = &rturn,
210     .vopts = {
211       .scaling = NCSCALE_STRETCH,
212       .blitter = NCBLIT_PIXEL,
213     },
214   };
215   m1.vopts.n = ncpile_create(nc, &nopts);
216   if(m1.vopts.n == NULL){
217     ncvisual_destroy(v1);
218     ncvisual_destroy(v2);
219     ncplane_destroy(label);
220     return -1;
221   }
222   struct marsh m2 = {
223     .id = 1,
224     .label = label,
225     .nc = nc,
226     .ncv = v2,
227     .done = &done,
228     .polyy = &polyy,
229     .polyx = &polyx,
230     .polypixel = &polypixel,
231     .turn = &turn,
232     .filled = &filled,
233     .rturn = &rturn,
234     .vopts = {
235       .scaling = NCSCALE_STRETCH,
236       .blitter = NCBLIT_PIXEL,
237     },
238   };
239   m2.vopts.n = ncpile_create(nc, &nopts);
240   if(m2.vopts.n == NULL){
241     ncvisual_destroy(v1);
242     ncvisual_destroy(v2);
243     ncplane_destroy(label);
244     ncplane_destroy(m1.vopts.n);
245     return -1;
246   }
247   if(bitmaps){
248     timespec_div(&demodelay, 10, &m1.tspec);
249     timespec_div(&demodelay, 10, &m2.tspec);
250   }
251   uint64_t basechan = 0;
252   ncchannels_set_bg_alpha(&basechan, NCALPHA_TRANSPARENT);
253   ncchannels_set_fg_alpha(&basechan, NCALPHA_TRANSPARENT);
254   ncplane_set_base(label, "", 0, basechan);
255   ncplane_set_bg_alpha(label, NCALPHA_TRANSPARENT);
256   ncplane_set_fg_rgb8(label, 0xff, 0xff, 0xff);
257   ncplane_set_styles(label, NCSTYLE_BOLD);
258   ncplane_printf_aligned(label, 0, NCALIGN_CENTER, "Yield: %03.1f%%", 0.0);
259   ncvgeom geom;
260   if(ncvisual_geom(nc, v1, &m1.vopts, &geom)){
261     ncplane_destroy(label);
262     ncvisual_destroy(v1);
263     ncvisual_destroy(v2);
264     ncplane_destroy(m2.vopts.n);
265     ncplane_destroy(m1.vopts.n);
266     return -1;
267   }
268   // we resize the visuals ahead of time, so that we're not rescaling them
269   // every time we call ncvisual_blit().
270   ncvisual_resize_noninterpolative(m1.ncv, geom.rpixy, geom.rpixx);
271   ncvisual_resize_noninterpolative(m2.ncv, geom.rpixy, geom.rpixx);
272   m1.maxy = m2.maxy = geom.rpixy;
273   m1.maxx = m2.maxx = geom.rpixx;
274 
275   pthread_t t1, t2;
276   int ret = 0;
277   // FIXME error checks
278   pthread_create(&t1, NULL, yielder_thread, &m1);
279   pthread_create(&t2, NULL, yielder_thread, &m2);
280   ret = pthread_join(t1, NULL) | pthread_join(t2, NULL);
281 
282   ncplane_destroy(label);
283   ncplane_reparent(m1.vopts.n, notcurses_stdplane(nc));
284   ncplane_reparent(m2.vopts.n, notcurses_stdplane(nc));
285   ncplane_destroy(m1.vopts.n);
286   ncplane_destroy(m2.vopts.n);
287   ncvisual_destroy(v1);
288   ncvisual_destroy(v2);
289   ncplane_erase(std);
290   return ret;
291 }
292