aboutsummaryrefslogtreecommitdiff
path: root/engines/tinsel/coroutine.h
blob: e0292735bb4c3ece6548660a73d4f355914210a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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 2
 * of the License, or (at your option) any later version.

 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL$
 * $Id$
 *
 */

#ifndef TINSEL_COROUTINE_H
#define TINSEL_COROUTINE_H

#include "common/scummsys.h"

namespace Tinsel {

/*
 * The following is loosely based on an article by Simon Tatham:
 *   <http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html>.
 * However, many improvements and tweaks have been made, in particular
 * by taking advantage of C++ features not available in C.
 * 
 * Why is this code here? Well, the Tinsel engine apparently used
 * setjmp/longjmp based coroutines as a core tool from the start, and
 * so they are deeply ingrained into the whole code base. When we
 * started to get Tinsel ready for ScummVM, we had to deal with that.
 * It soon got clear that we could not simply rewrite the code to work
 * without some form of coroutines. While possible in principle, it
 * would have meant a major restructuring of the entire code base, a
 * rather daunting task. Also, it would have very likely introduced
 * tons of regressons.
 * 
 * So instead of getting rid of the coroutines, we chose to implement
 * them in an alternate way, using Simon Tatham's trick as described
 * above. While the trick is dirty, the result seems to be clear enough,
 * we hope; plus, it allowed us to stay relatively close to the
 * original structure of the code, which made it easier to avoid
 * regressions, and will be helpful in the future when comparing things
 * against the original code base.
 */


/**
 * The core of any coroutine context which captures the 'state' of a coroutine.
 * Private use only
 */
struct CoroBaseContext {
	int _line;
	int _sleep;
	CoroBaseContext *_subctx;
	CoroBaseContext() : _line(0), _sleep(0), _subctx(0) {}
	~CoroBaseContext() { delete _subctx; }
};

typedef CoroBaseContext *CoroContext;


/**
 * Wrapper class which holds a pointer to a pointer to a CoroBaseContext.
 * The interesting part is the destructor, which kills the context being held,
 * but ONLY if the _sleep val of that context is zero. This way, a coroutine
 * can just 'return' w/o having to worry about freeing the allocated context
 * (in Simon Tatham's original code, one had to use a special macro to
 * return from a coroutine).
 */
class CoroContextHolder {
	CoroContext &_ctx;
public:
	CoroContextHolder(CoroContext &ctx) : _ctx(ctx) {}
	~CoroContextHolder() {
		if (_ctx && _ctx->_sleep == 0) {
			delete _ctx;
			_ctx = 0;
		}
	}
};


#define CORO_PARAM     CoroContext &coroParam

#define CORO_SUBCTX   coroParam->_subctx


#define CORO_BEGIN_CONTEXT  struct CoroContextTag : CoroBaseContext { int DUMMY
#define CORO_END_CONTEXT(x)    } *x = (CoroContextTag *)coroParam

#define CORO_BEGIN_CODE(x) \
		if (!x) {coroParam = x = new CoroContextTag();}\
		assert(coroParam);\
		assert(coroParam->_sleep >= 0);\
		coroParam->_sleep = 0;\
		CoroContextHolder tmpHolder(coroParam);\
		switch(coroParam->_line) { case 0:;

#define CORO_END_CODE \
		}

#define CORO_SLEEP(delay) \
		do {\
			coroParam->_line = __LINE__;\
			coroParam->_sleep = delay;\
			return; case __LINE__:;\
		} while (0)

/** Stop the currently running coroutine */
#define CORO_KILL_SELF()         do { coroParam->_sleep = -1; return; } while(0)

/** Invoke another coroutine */
#define CORO_INVOKE_ARGS(subCoro, ARGS)  \
		do {\
			coroParam->_line = __LINE__;\
			coroParam->_subctx = 0;\
			do {\
				subCoro ARGS;\
				if (!coroParam->_subctx) break;\
				coroParam->_sleep = coroParam->_subctx->_sleep;\
				return; case __LINE__:;\
			} while(1);\
		} while (0)

#define CORO_INVOKE_0(subCoroutine) \
			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX))
#define CORO_INVOKE_1(subCoroutine, a0) \
			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0))
#define CORO_INVOKE_2(subCoroutine, a0,a1) \
			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1))


} // end of namespace Tinsel

#endif		// TINSEL_COROUTINE_H