1/****************************************************************************
2 * Copyright (c) 1998-2006,2007 Free Software Foundation, Inc.                   *
3 *                                                                          *
4 * Permission is hereby granted, free of charge, to any person obtaining a  *
5 * copy of this software and associated documentation files (the            *
6 * "Software"), to deal in the Software without restriction, including      *
7 * without limitation the rights to use, copy, modify, merge, publish,      *
8 * distribute, distribute with modifications, sublicense, and/or sell       *
9 * copies of the Software, and to permit persons to whom the Software is    *
10 * furnished to do so, subject to the following conditions:                 *
11 *                                                                          *
12 * The above copyright notice and this permission notice shall be included  *
13 * in all copies or substantial portions of the Software.                   *
14 *                                                                          *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22 *                                                                          *
23 * Except as contained in this notice, the name(s) of the above copyright   *
24 * holders shall not be used in advertising or otherwise to promote the     *
25 * sale, use or other dealings in this Software without prior written       *
26 * authorization.                                                           *
27 ****************************************************************************/
28
29/****************************************************************************
30 *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31 *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32 ****************************************************************************/
33
34/******************************************************************************
35
36NAME
37   hashmap.c -- fill in scramble vector based on text hashes
38
39SYNOPSIS
40   void _nc_hash_map(void)
41
42DESCRIPTION:
43   This code attempts to recognize pairs of old and new lines in the physical
44and virtual screens.  When a line pair is recognized, the old line index is
45placed in the oldindex member of the virtual screen line, to be used by the
46vertical-motion optimizer portion of the update logic (see hardscroll.c).
47
48   Line pairs are recognized by applying a modified Heckel's algorithm,
49sped up by hashing.  If a line hash is unique in both screens, those
50lines must be a pair. Then if the lines just before or after the pair
51are the same or similar, they are a pair too.
52
53   We don't worry about false pairs produced by hash collisions, on the
54assumption that such cases are rare and will only make the latter stages
55of update less efficient, not introduce errors.
56
57HOW TO TEST THIS:
58
59Use the following production:
60
61hashmap: hashmap.c
62	$(CC) -g -DHASHDEBUG hashmap.c hardscroll.c ../objects/lib_trace.o -o hashmap
63
64AUTHOR
65    Eric S. Raymond <esr@snark.thyrsus.com>, May 1996
66    Bug fixes and improvements by Alexander V. Lukyanov <lav@yars.free.net>, 1997
67
68*****************************************************************************/
69
70#include <curses.priv.h>
71#include <term.h>		/* for back_color_erase */
72
73MODULE_ID("$Id: hashmap.c,v 1.56 2007/10/13 18:47:25 Miroslav.Lichvar Exp $")
74
75#ifdef HASHDEBUG
76
77# define _tracef	printf
78# undef TR
79# define TR(n, a)	if (_nc_tracing & (n)) { _tracef a ; putchar('\n'); }
80# undef screen_lines
81# define screen_lines MAXLINES
82# define TEXTWIDTH	1
83int oldnums[MAXLINES], reallines[MAXLINES];
84static NCURSES_CH_T oldtext[MAXLINES][TEXTWIDTH];
85static NCURSES_CH_T newtext[MAXLINES][TEXTWIDTH];
86# define OLDNUM(n)	oldnums[n]
87# define OLDTEXT(n)	oldtext[n]
88# define NEWTEXT(m)	newtext[m]
89# define PENDING(n)     1
90
91#else /* !HASHDEBUG */
92
93# define OLDNUM(n)	SP->_oldnum_list[n]
94# define OLDTEXT(n)	curscr->_line[n].text
95# define NEWTEXT(m)	newscr->_line[m].text
96# define TEXTWIDTH	(curscr->_maxx+1)
97# define PENDING(n)     (newscr->_line[n].firstchar != _NOCHANGE)
98
99#endif /* !HASHDEBUG */
100
101#define oldhash		(SP->oldhash)
102#define newhash		(SP->newhash)
103#define hashtab		(SP->hashtab)
104#define lines_alloc	(SP->hashtab_len)
105
106#if USE_WIDEC_SUPPORT
107#define HASH_VAL(ch) (ch.chars[0])
108#else
109#define HASH_VAL(ch) (ch)
110#endif
111
112static const NCURSES_CH_T blankchar = NewChar(BLANK_TEXT);
113
114static NCURSES_INLINE unsigned long
115hash(NCURSES_CH_T * text)
116{
117    int i;
118    NCURSES_CH_T ch;
119    unsigned long result = 0;
120    for (i = TEXTWIDTH; i > 0; i--) {
121	ch = *text++;
122	result += (result << 5) + HASH_VAL(ch);
123    }
124    return result;
125}
126
127/* approximate update cost */
128static int
129update_cost(NCURSES_CH_T * from, NCURSES_CH_T * to)
130{
131    int cost = 0;
132    int i;
133
134    for (i = TEXTWIDTH; i > 0; i--, from++, to++)
135	if (!(CharEq(*from, *to)))
136	    cost++;
137
138    return cost;
139}
140
141static int
142update_cost_from_blank(NCURSES_CH_T * to)
143{
144    int cost = 0;
145    int i;
146    NCURSES_CH_T blank = blankchar;
147
148    if (back_color_erase)
149	SetPair(blank, GetPair(stdscr->_nc_bkgd));
150
151    for (i = TEXTWIDTH; i > 0; i--, to++)
152	if (!(CharEq(blank, *to)))
153	    cost++;
154
155    return cost;
156}
157
158/*
159 * Returns true when moving line 'from' to line 'to' seems to be cost
160 * effective. 'blank' indicates whether the line 'to' would become blank.
161 */
162static NCURSES_INLINE bool
163cost_effective(const int from, const int to, const bool blank)
164{
165    int new_from;
166
167    if (from == to)
168	return FALSE;
169
170    new_from = OLDNUM(from);
171    if (new_from == _NEWINDEX)
172	new_from = from;
173
174    /*
175     * On the left side of >= is the cost before moving;
176     * on the right side -- cost after moving.
177     */
178    return (((blank ? update_cost_from_blank(NEWTEXT(to))
179	      : update_cost(OLDTEXT(to), NEWTEXT(to)))
180	     + update_cost(OLDTEXT(new_from), NEWTEXT(from)))
181	    >= ((new_from == from ? update_cost_from_blank(NEWTEXT(from))
182		 : update_cost(OLDTEXT(new_from), NEWTEXT(from)))
183		+ update_cost(OLDTEXT(from), NEWTEXT(to)))) ? TRUE : FALSE;
184}
185
186static void
187grow_hunks(void)
188{
189    int start, end, shift;
190    int back_limit, forward_limit;	/* limits for cells to fill */
191    int back_ref_limit, forward_ref_limit;	/* limits for refrences */
192    int i;
193    int next_hunk;
194
195    /*
196     * This is tricky part.  We have unique pairs to use as anchors.
197     * Use these to deduce the presence of spans of identical lines.
198     */
199    back_limit = 0;
200    back_ref_limit = 0;
201
202    i = 0;
203    while (i < screen_lines && OLDNUM(i) == _NEWINDEX)
204	i++;
205    for (; i < screen_lines; i = next_hunk) {
206	start = i;
207	shift = OLDNUM(i) - i;
208
209	/* get forward limit */
210	i = start + 1;
211	while (i < screen_lines && OLDNUM(i) != _NEWINDEX && OLDNUM(i) - i
212	       == shift)
213	    i++;
214	end = i;
215	while (i < screen_lines && OLDNUM(i) == _NEWINDEX)
216	    i++;
217	next_hunk = i;
218	forward_limit = i;
219	if (i >= screen_lines || OLDNUM(i) >= i)
220	    forward_ref_limit = i;
221	else
222	    forward_ref_limit = OLDNUM(i);
223
224	i = start - 1;
225	/* grow back */
226	if (shift < 0)
227	    back_limit = back_ref_limit + (-shift);
228	while (i >= back_limit) {
229	    if (newhash[i] == oldhash[i + shift]
230		|| cost_effective(i + shift, i, shift < 0)) {
231		OLDNUM(i) = i + shift;
232		TR(TRACE_UPDATE | TRACE_MOVE,
233		   ("connected new line %d to old line %d (backward continuation)",
234		    i, i + shift));
235	    } else {
236		TR(TRACE_UPDATE | TRACE_MOVE,
237		   ("not connecting new line %d to old line %d (backward continuation)",
238		    i, i + shift));
239		break;
240	    }
241	    i--;
242	}
243
244	i = end;
245	/* grow forward */
246	if (shift > 0)
247	    forward_limit = forward_ref_limit - shift;
248	while (i < forward_limit) {
249	    if (newhash[i] == oldhash[i + shift]
250		|| cost_effective(i + shift, i, shift > 0)) {
251		OLDNUM(i) = i + shift;
252		TR(TRACE_UPDATE | TRACE_MOVE,
253		   ("connected new line %d to old line %d (forward continuation)",
254		    i, i + shift));
255	    } else {
256		TR(TRACE_UPDATE | TRACE_MOVE,
257		   ("not connecting new line %d to old line %d (forward continuation)",
258		    i, i + shift));
259		break;
260	    }
261	    i++;
262	}
263
264	back_ref_limit = back_limit = i;
265	if (shift > 0)
266	    back_ref_limit += shift;
267    }
268}
269
270NCURSES_EXPORT(void)
271_nc_hash_map(void)
272{
273    HASHMAP *sp;
274    register int i;
275    int start, shift, size;
276
277    if (screen_lines > lines_alloc) {
278	if (hashtab)
279	    free(hashtab);
280	hashtab = typeMalloc(HASHMAP, (screen_lines + 1) * 2);
281	if (!hashtab) {
282	    if (oldhash) {
283		FreeAndNull(oldhash);
284	    }
285	    lines_alloc = 0;
286	    return;
287	}
288	lines_alloc = screen_lines;
289    }
290
291    if (oldhash && newhash) {
292	/* re-hash only changed lines */
293	for (i = 0; i < screen_lines; i++) {
294	    if (PENDING(i))
295		newhash[i] = hash(NEWTEXT(i));
296	}
297    } else {
298	/* re-hash all */
299	if (oldhash == 0)
300	    oldhash = typeCalloc(unsigned long, (unsigned) screen_lines);
301	if (newhash == 0)
302	    newhash = typeCalloc(unsigned long, (unsigned) screen_lines);
303	if (!oldhash || !newhash)
304	    return;		/* malloc failure */
305	for (i = 0; i < screen_lines; i++) {
306	    newhash[i] = hash(NEWTEXT(i));
307	    oldhash[i] = hash(OLDTEXT(i));
308	}
309    }
310
311#ifdef HASH_VERIFY
312    for (i = 0; i < screen_lines; i++) {
313	if (newhash[i] != hash(NEWTEXT(i)))
314	    fprintf(stderr, "error in newhash[%d]\n", i);
315	if (oldhash[i] != hash(OLDTEXT(i)))
316	    fprintf(stderr, "error in oldhash[%d]\n", i);
317    }
318#endif
319
320    /*
321     * Set up and count line-hash values.
322     */
323    memset(hashtab, '\0', sizeof(*hashtab) * (screen_lines + 1) * 2);
324    for (i = 0; i < screen_lines; i++) {
325	unsigned long hashval = oldhash[i];
326
327	for (sp = hashtab; sp->hashval; sp++)
328	    if (sp->hashval == hashval)
329		break;
330	sp->hashval = hashval;	/* in case this is a new entry */
331	sp->oldcount++;
332	sp->oldindex = i;
333    }
334    for (i = 0; i < screen_lines; i++) {
335	unsigned long hashval = newhash[i];
336
337	for (sp = hashtab; sp->hashval; sp++)
338	    if (sp->hashval == hashval)
339		break;
340	sp->hashval = hashval;	/* in case this is a new entry */
341	sp->newcount++;
342	sp->newindex = i;
343
344	OLDNUM(i) = _NEWINDEX;	/* initialize old indices array */
345    }
346
347    /*
348     * Mark line pairs corresponding to unique hash pairs.
349     *
350     * We don't mark lines with offset 0, because it can make fail
351     * extending hunks by cost_effective. Otherwise, it does not
352     * have any side effects.
353     */
354    for (sp = hashtab; sp->hashval; sp++)
355	if (sp->oldcount == 1 && sp->newcount == 1
356	    && sp->oldindex != sp->newindex) {
357	    TR(TRACE_UPDATE | TRACE_MOVE,
358	       ("new line %d is hash-identical to old line %d (unique)",
359		sp->newindex, sp->oldindex));
360	    OLDNUM(sp->newindex) = sp->oldindex;
361	}
362
363    grow_hunks();
364
365    /*
366     * Eliminate bad or impossible shifts -- this includes removing
367     * those hunks which could not grow because of conflicts, as well
368     * those which are to be moved too far, they are likely to destroy
369     * more than carry.
370     */
371    for (i = 0; i < screen_lines;) {
372	while (i < screen_lines && OLDNUM(i) == _NEWINDEX)
373	    i++;
374	if (i >= screen_lines)
375	    break;
376	start = i;
377	shift = OLDNUM(i) - i;
378	i++;
379	while (i < screen_lines && OLDNUM(i) != _NEWINDEX && OLDNUM(i) - i
380	       == shift)
381	    i++;
382	size = i - start;
383	if (size < 3 || size + min(size / 8, 2) < abs(shift)) {
384	    while (start < i) {
385		OLDNUM(start) = _NEWINDEX;
386		start++;
387	    }
388	}
389    }
390
391    /* After clearing invalid hunks, try grow the rest. */
392    grow_hunks();
393}
394
395NCURSES_EXPORT(void)
396_nc_make_oldhash(int i)
397{
398    if (oldhash)
399	oldhash[i] = hash(OLDTEXT(i));
400}
401
402NCURSES_EXPORT(void)
403_nc_scroll_oldhash(int n, int top, int bot)
404{
405    size_t size;
406    int i;
407
408    if (!oldhash)
409	return;
410
411    size = sizeof(*oldhash) * (bot - top + 1 - abs(n));
412    if (n > 0) {
413	memmove(oldhash + top, oldhash + top + n, size);
414	for (i = bot; i > bot - n; i--)
415	    oldhash[i] = hash(OLDTEXT(i));
416    } else {
417	memmove(oldhash + top - n, oldhash + top, size);
418	for (i = top; i < top - n; i++)
419	    oldhash[i] = hash(OLDTEXT(i));
420    }
421}
422
423#ifdef HASHDEBUG
424static void
425usage(void)
426{
427    static const char *table[] =
428    {
429	"hashmap test-driver",
430	"",
431	"#  comment",
432	"l  get initial line number vector",
433	"n  use following letters as text of new lines",
434	"o  use following letters as text of old lines",
435	"d  dump state of test arrays",
436	"h  apply hash mapper and see scroll optimization",
437	"?  this message"
438    };
439    size_t n;
440    for (n = 0; n < sizeof(table) / sizeof(table[0]); n++)
441	fprintf(stderr, "%s\n", table[n]);
442}
443
444int
445main(int argc GCC_UNUSED, char *argv[]GCC_UNUSED)
446{
447    char line[BUFSIZ], *st;
448    int n;
449
450    if (setupterm(NULL, fileno(stdout), (int *) 0) == ERR)
451	return EXIT_FAILURE;
452    (void) _nc_alloc_screen();
453
454    for (n = 0; n < screen_lines; n++) {
455	reallines[n] = n;
456	oldnums[n] = _NEWINDEX;
457	CharOf(oldtext[n][0]) = CharOf(newtext[n][0]) = '.';
458    }
459
460    if (isatty(fileno(stdin)))
461	usage();
462
463#ifdef TRACE
464    _nc_tracing = TRACE_MOVE;
465#endif
466    for (;;) {
467	/* grab a test command */
468	if (fgets(line, sizeof(line), stdin) == (char *) NULL)
469	    break;
470
471	switch (line[0]) {
472	case '#':		/* comment */
473	    (void) fputs(line, stderr);
474	    break;
475
476	case 'l':		/* get initial line number vector */
477	    for (n = 0; n < screen_lines; n++) {
478		reallines[n] = n;
479		oldnums[n] = _NEWINDEX;
480	    }
481	    n = 0;
482	    st = strtok(line, " ");
483	    do {
484		oldnums[n++] = atoi(st);
485	    } while
486		((st = strtok((char *) NULL, " ")) != 0);
487	    break;
488
489	case 'n':		/* use following letters as text of new lines */
490	    for (n = 0; n < screen_lines; n++)
491		CharOf(newtext[n][0]) = '.';
492	    for (n = 0; n < screen_lines; n++)
493		if (line[n + 1] == '\n')
494		    break;
495		else
496		    CharOf(newtext[n][0]) = line[n + 1];
497	    break;
498
499	case 'o':		/* use following letters as text of old lines */
500	    for (n = 0; n < screen_lines; n++)
501		CharOf(oldtext[n][0]) = '.';
502	    for (n = 0; n < screen_lines; n++)
503		if (line[n + 1] == '\n')
504		    break;
505		else
506		    CharOf(oldtext[n][0]) = line[n + 1];
507	    break;
508
509	case 'd':		/* dump state of test arrays */
510#ifdef TRACE
511	    _nc_linedump();
512#endif
513	    (void) fputs("Old lines: [", stdout);
514	    for (n = 0; n < screen_lines; n++)
515		putchar(CharOf(oldtext[n][0]));
516	    putchar(']');
517	    putchar('\n');
518	    (void) fputs("New lines: [", stdout);
519	    for (n = 0; n < screen_lines; n++)
520		putchar(CharOf(newtext[n][0]));
521	    putchar(']');
522	    putchar('\n');
523	    break;
524
525	case 'h':		/* apply hash mapper and see scroll optimization */
526	    _nc_hash_map();
527	    (void) fputs("Result:\n", stderr);
528#ifdef TRACE
529	    _nc_linedump();
530#endif
531	    _nc_scroll_optimize();
532	    (void) fputs("Done.\n", stderr);
533	    break;
534	default:
535	case '?':
536	    usage();
537	    break;
538	}
539    }
540#if NO_LEAKS
541    _nc_free_and_exit(EXIT_SUCCESS);
542#else
543    return EXIT_SUCCESS;
544#endif
545}
546
547#endif /* HASHDEBUG */
548
549/* hashmap.c ends here */
550