1 /** @file thinker.cpp  Base for all thinkers.
2  *
3  * @authors Copyright (c) 2014-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  *
5  * @par License
6  * GPL: http://www.gnu.org/licenses/gpl.html
7  *
8  * <small>This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by the
10  * Free Software Foundation; either version 2 of the License, or (at your
11  * option) any later version. This program is distributed in the hope that it
12  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14  * Public License for more details. You should have received a copy of the GNU
15  * General Public License along with this program; if not, write to the Free
16  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17  * 02110-1301 USA</small>
18  */
19 
20 #include "doomsday/world/thinker.h"
21 
22 #include <de/math.h>
23 #include <de/memory.h>
24 #include <de/memoryzone.h>
25 
26 using namespace de;
27 
DENG2_PIMPL_NOREF(Thinker)28 DENG2_PIMPL_NOREF(Thinker)
29 {
30     dsize size;
31     thinker_s *base;    // owned
32     IData *data;        // owned, optional
33 
34     Impl(AllocMethod alloc, dsize sizeInBytes, IData *data_)
35         : size(max<dsize>(sizeInBytes, sizeof(thinker_s)))
36         , base(0)
37         , data(data_)
38     {
39         if (alloc == AllocateStandard)
40         {
41             base = reinterpret_cast<thinker_s *>(M_Calloc(size));
42             base->_flags = THINKF_STD_MALLOC;
43         }
44         else // using memory zone
45         {
46             base = reinterpret_cast<thinker_s *>(Z_Calloc(size, PU_MAP, NULL));
47         }
48 
49         if (data) data->setThinker(base);
50     }
51 
52     Impl(Impl const &other)
53         : size(other.size)
54         , base(reinterpret_cast<thinker_s *>(other.base->_flags & THINKF_STD_MALLOC?
55                                                  M_MemDup(other.base, size) :
56                                                  Z_MemDup(other.base, size)))
57         , data(other.data? other.data->duplicate() : 0)
58     {
59         base->d = data;
60         if (data) data->setThinker(base);
61     }
62 
63     Impl(thinker_s *podThinkerToTake, dsize sizeInBytes)
64         : size(sizeInBytes)
65         , base(podThinkerToTake)
66         , data(reinterpret_cast<IData *>(podThinkerToTake->d)) // also take ownership of the private data
67     {}
68 
69     ~Impl()
70     {
71         release();
72     }
73 
74     void release()
75     {
76         if (base)
77         {
78             if (isStandardAllocated())
79             {
80                 M_Free(base);
81             }
82             else
83             {
84                 Z_Free(base);
85             }
86         }
87 
88         // Get rid of the private data, too.
89         delete data;
90     }
91 
92     bool isStandardAllocated() const
93     {
94         return base && (base->_flags & THINKF_STD_MALLOC);
95     }
96 
97     void relinquish()
98     {
99         base = 0;
100         data = 0;
101         size = 0;
102     }
103 
104     static void clearBaseToZero(thinker_s *base, dsize size)
105     {
106         bool const stdAlloc = CPP_BOOL(base->_flags & THINKF_STD_MALLOC);
107         memset(base, 0, size);
108         if (stdAlloc) base->_flags |= THINKF_STD_MALLOC;
109     }
110 };
111 
112 #define STRUCT_MEMBER_ACCESSORS() \
113       prev    (*this, offsetof(thinker_s, prev    )) \
114     , next    (*this, offsetof(thinker_s, next    )) \
115     , function(*this, offsetof(thinker_s, function)) \
116     , id      (*this, offsetof(thinker_s, id      ))
117 
Thinker(dsize sizeInBytes,IData * data)118 Thinker::Thinker(dsize sizeInBytes, IData *data)
119     : d(new Impl(AllocateStandard, sizeInBytes, data))
120     , STRUCT_MEMBER_ACCESSORS()
121 {
122     // Default to no public thinker callback.
123     function = Thinker_NoOperation;
124 }
125 
Thinker(AllocMethod alloc,dsize sizeInBytes,Thinker::IData * data)126 Thinker::Thinker(AllocMethod alloc, dsize sizeInBytes, Thinker::IData *data)
127     : d(new Impl(alloc, sizeInBytes, data))
128     , STRUCT_MEMBER_ACCESSORS()
129 {
130     // Default to no public thinker callback.
131     function = Thinker_NoOperation;
132 }
133 
Thinker(Thinker const & other)134 Thinker::Thinker(Thinker const &other)
135     : d(new Impl(*other.d))
136     , STRUCT_MEMBER_ACCESSORS()
137 {}
138 
Thinker(thinker_s const & podThinker,dsize sizeInBytes,AllocMethod alloc)139 Thinker::Thinker(thinker_s const &podThinker, dsize sizeInBytes, AllocMethod alloc)
140     : d(new Impl(alloc, sizeInBytes, 0))
141     , STRUCT_MEMBER_ACCESSORS()
142 {
143     DENG2_ASSERT(d->size == sizeInBytes);
144     memcpy(d->base, &podThinker, sizeInBytes);
145 
146     // Retain the original allocation flag, though.
147     d->base->_flags &= ~THINKF_STD_MALLOC;
148     if (alloc == AllocateStandard) d->base->_flags |= THINKF_STD_MALLOC;
149 
150     if (podThinker.d)
151     {
152         setData(reinterpret_cast<IData *>(podThinker.d)->duplicate());
153     }
154 }
155 
Thinker(thinker_s * podThinkerToTake,de::dsize sizeInBytes)156 Thinker::Thinker(thinker_s *podThinkerToTake, de::dsize sizeInBytes)
157     : d(new Impl(podThinkerToTake, sizeInBytes))
158     , STRUCT_MEMBER_ACCESSORS()
159 {}
160 
operator =(Thinker const & other)161 Thinker &Thinker::operator = (Thinker const &other)
162 {
163     d.reset(new Impl(*other.d));
164     return *this;
165 }
166 
enable(bool yes)167 void Thinker::enable(bool yes)
168 {
169     applyFlagOperation(d->base->_flags, duint32(THINKF_DISABLED), yes? SetFlags : UnsetFlags);
170 }
171 
zap()172 void Thinker::zap()
173 {
174     delete d->data;
175     d->data = 0;
176 
177     Impl::clearBaseToZero(d->base, d->size);
178 }
179 
isDisabled() const180 bool Thinker::isDisabled() const
181 {
182     return (d->base->_flags & THINKF_DISABLED) != 0;
183 }
184 
base()185 thinker_s &Thinker::base()
186 {
187     return *d->base;
188 }
189 
base() const190 thinker_s const &Thinker::base() const
191 {
192     return *d->base;
193 }
194 
hasData() const195 bool Thinker::hasData() const
196 {
197     return d->data != 0;
198 }
199 
data()200 Thinker::IData &Thinker::data()
201 {
202     DENG2_ASSERT(hasData());
203     return *d->data;
204 }
205 
data() const206 Thinker::IData const &Thinker::data() const
207 {
208     DENG2_ASSERT(hasData());
209     return *d->data;
210 }
211 
sizeInBytes() const212 dsize Thinker::sizeInBytes() const
213 {
214     return d->size;
215 }
216 
take()217 thinker_s *Thinker::take()
218 {
219     DENG2_ASSERT(d->base->d == d->data);
220 
221     thinker_s *th = d->base;
222     d->relinquish();
223     return th;
224 }
225 
destroy(thinker_s * thinkerBase)226 void Thinker::destroy(thinker_s *thinkerBase)
227 {
228     DENG2_ASSERT(thinkerBase != 0);
229 
230     release(*thinkerBase);
231 
232     if (thinkerBase->_flags & THINKF_STD_MALLOC)
233     {
234         M_Free(thinkerBase);
235     }
236     else
237     {
238         Z_Free(thinkerBase);
239     }
240 }
241 
release(thinker_s & thinkerBase)242 void Thinker::release(thinker_s &thinkerBase)
243 {
244     delete reinterpret_cast<IData *>(thinkerBase.d);
245     thinkerBase.d = 0;
246 }
247 
zap(thinker_s & thinkerBase,dsize sizeInBytes)248 void Thinker::zap(thinker_s &thinkerBase, dsize sizeInBytes)
249 {
250     delete reinterpret_cast<IData *>(thinkerBase.d);
251     Impl::clearBaseToZero(&thinkerBase, sizeInBytes);
252 }
253 
setData(Thinker::IData * data)254 void Thinker::setData(Thinker::IData *data)
255 {
256     if (d->data) delete d->data;
257 
258     d->data    = data;
259     d->base->d = data;
260 
261     if (data)
262     {
263         data->setThinker(*this);
264     }
265 }
266 
Thinker_InStasis(thinker_s const * thinker)267 dd_bool Thinker_InStasis(thinker_s const *thinker)
268 {
269     if (!thinker) return false;
270     return (thinker->_flags & THINKF_DISABLED) != 0;
271 }
272 
Thinker_SetStasis(thinker_t * thinker,dd_bool on)273 void Thinker_SetStasis(thinker_t *thinker, dd_bool on)
274 {
275     if (thinker)
276     {
277         applyFlagOperation(thinker->_flags, duint32(THINKF_DISABLED),
278                            on? SetFlags : UnsetFlags);
279     }
280 }
281 
Thinker_NoOperation(void *)282 void Thinker_NoOperation(void *)
283 {
284     // do nothing
285 }
286