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