Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ScriptGlue.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 // This module defines the table of all functions callable from JS.
19 // it's required by the interpreter; we make use of the opportunity to
20 // document them all in one spot. we thus obviate having to dig through
21 // all the other headers. most of the functions are implemented here;
22 // as for the rest, we only link to their docs (duplication is bad).
23 
24 #include "precompiled.h"
25 
26 #include "ScriptGlue.h"
27 #include "JSConversions.h"
28 
29 #include "graphics/GameView.h"
30 #include "graphics/LightEnv.h"
31 #include "graphics/MapWriter.h"
32 #include "graphics/Unit.h"
33 #include "graphics/UnitManager.h"
34 #include "gui/GUIManager.h"
35 #include "gui/IGUIObject.h"
36 #include "lib/frequency_filter.h"
37 #include "lib/svn_revision.h"
38 #include "lib/timer.h"
39 #include "lib/sysdep/sysdep.h" // sys_OpenFile
41 #include "network/NetServer.h"
42 #include "ps/CConsole.h"
43 #include "ps/CLogger.h"
44 #include "ps/CStr.h"
45 #include "ps/Game.h"
46 #include "ps/Globals.h" // g_frequencyFilter
47 #include "ps/GameSetup/GameSetup.h"
48 #include "ps/Hotkey.h"
49 #include "ps/ProfileViewer.h"
50 #include "ps/World.h"
53 #include "renderer/Renderer.h"
57 
58 // rationale: the function table is now at the end of the source file to
59 // avoid the need for forward declarations for every function.
60 
61 // all normal function wrappers have the following signature:
62 // JSBool func(JSContext* cx, JSObject* globalObject, uintN argc, jsval* argv, jsval* rval);
63 // all property accessors have the following signature:
64 // JSBool accessor(JSContext* cx, JSObject* globalObject, jsval id, jsval* vp);
65 
66 
67 //-----------------------------------------------------------------------------
68 // Timer
69 //-----------------------------------------------------------------------------
70 
71 
72 // Script profiling functions: Begin timing a piece of code with StartJsTimer(num)
73 // and stop timing with StopJsTimer(num). The results will be printed to stdout
74 // when the game exits.
75 
76 static const size_t MAX_JS_TIMERS = 20;
80 static wchar_t js_timer_descriptions_buf[MAX_JS_TIMERS * 12]; // depends on MAX_JS_TIMERS and format string below
81 
82 static void InitJsTimers()
83 {
84  wchar_t* pos = js_timer_descriptions_buf;
85  for(size_t i = 0; i < MAX_JS_TIMERS; i++)
86  {
87  const wchar_t* description = pos;
88  pos += swprintf_s(pos, 12, L"js_timer %d", (int)i)+1;
89  timer_AddClient(&js_timer_clients[i], description);
90  }
91 
92  // call several times to get a good approximation of 'hot' performance.
93  // note: don't use a separate timer slot to warm up and then judge
94  // overhead from another: that causes worse results (probably some
95  // caching effects inside JS, but I don't entirely understand why).
96  static const char* calibration_script =
97  "startXTimer(0);\n"
98  "stopXTimer (0);\n"
99  "\n";
100  g_ScriptingHost.RunMemScript(calibration_script, strlen(calibration_script));
101  // slight hack: call RunMemScript twice because we can't average several
102  // TimerUnit values because there's no operator/. this way is better anyway
103  // because it hopefully avoids the one-time JS init overhead.
104  g_ScriptingHost.RunMemScript(calibration_script, strlen(calibration_script));
105  js_timer_overhead = js_timer_clients[0].sum;
106  js_timer_clients[0].sum.SetToZero();
107 }
108 
109 JSBool StartJsTimer(JSContext* cx, uintN argc, jsval* vp)
110 {
111  ONCE(InitJsTimers());
112 
114  size_t slot = ToPrimitive<size_t>(JS_ARGV(cx, vp)[0]);
115  if (slot >= MAX_JS_TIMERS)
116  return JS_FALSE;
117 
118  js_start_times[slot].SetFromTimer();
119 
120  JS_SET_RVAL(cx, vp, JSVAL_VOID);
121  return JS_TRUE;
122 }
123 
124 
125 JSBool StopJsTimer(JSContext* cx, uintN argc, jsval* vp)
126 {
128  size_t slot = ToPrimitive<size_t>(JS_ARGV(cx, vp)[0]);
129  if (slot >= MAX_JS_TIMERS)
130  return JS_FALSE;
131 
132  TimerUnit now;
133  now.SetFromTimer();
134  now.Subtract(js_timer_overhead);
135  BillingPolicy_Default()(&js_timer_clients[slot], js_start_times[slot], now);
136  js_start_times[slot].SetToZero();
137 
138  JS_SET_RVAL(cx, vp, JSVAL_VOID);
139  return JS_TRUE;
140 }
141 
142 
143 //-----------------------------------------------------------------------------
144 // Game Setup
145 //-----------------------------------------------------------------------------
146 
147 // Immediately ends the current game (if any).
148 // params:
149 // returns:
150 JSBool EndGame(JSContext* cx, uintN argc, jsval* vp)
151 {
153 
154  EndGame();
155 
156  JS_SET_RVAL(cx, vp, JSVAL_VOID);
157  return JS_TRUE;
158 }
159 
160 //-----------------------------------------------------------------------------
161 // Misc. Engine Interface
162 //-----------------------------------------------------------------------------
163 
164 // Return the global frames-per-second value.
165 // params:
166 // returns: FPS [int]
167 // notes:
168 // - This value is recalculated once a frame. We take special care to
169 // filter it, so it is both accurate and free of jitter.
170 JSBool GetFps(JSContext* cx, uintN argc, jsval* vp)
171 {
173  int freq = 0;
174  if (g_frequencyFilter)
175  freq = g_frequencyFilter->StableFrequency();
176  JS_SET_RVAL(cx, vp, INT_TO_JSVAL(freq));
177  return JS_TRUE;
178 }
179 
180 
181 // Cause the game to exit gracefully.
182 // params:
183 // returns:
184 // notes:
185 // - Exit happens after the current main loop iteration ends
186 // (since this only sets a flag telling it to end)
187 JSBool ExitProgram(JSContext* cx, uintN argc, jsval* vp)
188 {
190 
191  kill_mainloop();
192 
193  JS_SET_RVAL(cx, vp, JSVAL_VOID);
194  return JS_TRUE;
195 }
196 
197 
198 // Change the mouse cursor.
199 // params: cursor name [string] (i.e. basename of definition file and texture)
200 // returns:
201 // notes:
202 // - Cursors are stored in "art\textures\cursors"
203 JSBool SetCursor(JSContext* cx, uintN argc, jsval* vp)
204 {
206  g_CursorName = g_ScriptingHost.ValueToUCString(JS_ARGV(cx, vp)[0]);
207 
208  JS_SET_RVAL(cx, vp, JSVAL_VOID);
209  return JS_TRUE;
210 }
211 
212 JSBool GetGUIObjectByName(JSContext* cx, uintN argc, jsval* vp)
213 {
215 
216  try
217  {
218  CStr name = ToPrimitive<CStr>(cx, JS_ARGV(cx, vp)[0]);
219  IGUIObject* guiObj = g_GUI->FindObjectByName(name);
220  if (guiObj)
221  JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(guiObj->GetJSObject()));
222  else
223  JS_SET_RVAL(cx, vp, JSVAL_NULL);
224  return JS_TRUE;
225  }
226  catch (PSERROR_Scripting&)
227  {
228  return JS_FALSE;
229  }
230 }
231 
232 //-----------------------------------------------------------------------------
233 // Miscellany
234 //-----------------------------------------------------------------------------
235 
236 // Return the date/time at which the current executable was compiled.
237 // params: none (-> "date time (svn revision)") OR an integer specifying
238 // what to display: 0 for date, 1 for time, 2 for svn revision
239 // returns: string with the requested timestamp info
240 // notes:
241 // - Displayed on main menu screen; tells non-programmers which auto-build
242 // they are running. Could also be determined via .EXE file properties,
243 // but that's a bit more trouble.
244 // - To be exact, the date/time returned is when scriptglue.cpp was
245 // last compiled, but the auto-build does full rebuilds.
246 // - svn revision is generated by calling svnversion and cached in
247 // lib/svn_revision.cpp. it is useful to know when attempting to
248 // reproduce bugs (the main EXE and PDB should be temporarily reverted to
249 // that revision so that they match user-submitted crashdumps).
250 JSBool GetBuildTimestamp(JSContext* cx, uintN argc, jsval* vp)
251 {
253 
254  char buf[200];
255 
256  // see function documentation
257  const int mode = argc? JSVAL_TO_INT(JS_ARGV(cx, vp)[0]) : -1;
258  switch(mode)
259  {
260  case -1:
261  sprintf_s(buf, ARRAY_SIZE(buf), "%s %s (%ls)", __DATE__, __TIME__, svn_revision);
262  break;
263  case 0:
264  sprintf_s(buf, ARRAY_SIZE(buf), "%s", __DATE__);
265  break;
266  case 1:
267  sprintf_s(buf, ARRAY_SIZE(buf), "%s", __TIME__);
268  break;
269  case 2:
270  sprintf_s(buf, ARRAY_SIZE(buf), "%ls", svn_revision);
271  break;
272  }
273 
274  JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, buf)));
275  return JS_TRUE;
276 }
277 
278 #if MOZJS_DEBUG_ABI
279 void DumpHeap(const char* basename, int idx, JSContext* cx)
280 {
281  char filename[64];
282  sprintf_s(filename, ARRAY_SIZE(filename), "%s.%03d.txt", basename, idx);
283  OsPath pathname = psLogDir() / filename;
284  FILE* f = sys_OpenFile(pathname, "w");
285  ENSURE(f);
286  JS_DumpHeap(cx, f, NULL, 0, NULL, (size_t)-1, NULL);
287  fclose(f);
288 }
289 #endif
290 
291 JSBool DumpHeaps(JSContext* cx, uintN argc, jsval* vp)
292 {
293  UNUSED2(cx);
294  UNUSED2(argc);
295 
296 #if MOZJS_DEBUG_ABI
297  static int i = 0;
298 
300  DumpHeap("gui", i, g_ScriptingHost.GetContext());
301  if (g_Game)
302  DumpHeap("sim", i, g_Game->GetSimulation2()->GetScriptInterface().GetContext());
303 
304  ++i;
305 #else
306  debug_warn(L"DumpHeaps only available in DEBUG mode");
307 #endif
308 
309  JS_SET_RVAL(cx, vp, JSVAL_VOID);
310  return JS_TRUE;
311 }
312 
313 //-----------------------------------------------------------------------------
314 
315 // Is the game paused?
316 JSBool IsPaused(JSContext* cx, uintN argc, jsval* vp)
317 {
319 
320  if (!g_Game)
321  {
322  JS_ReportError(cx, "Game is not started");
323  return JS_FALSE;
324  }
325 
326  JS_SET_RVAL(cx, vp, g_Game->m_Paused ? JSVAL_TRUE : JSVAL_FALSE);
327  return JS_TRUE;
328 }
329 
330 // Pause/unpause the game
331 JSBool SetPaused(JSContext* cx, uintN argc, jsval* vp)
332 {
333  JSU_REQUIRE_PARAMS( 1 );
334 
335  if (!g_Game)
336  {
337  JS_ReportError(cx, "Game is not started");
338  return JS_FALSE;
339  }
340 
341  try
342  {
343  g_Game->m_Paused = ToPrimitive<bool> (JS_ARGV(cx, vp)[0]);
344 
345  if ( g_SoundManager )
347  }
349  {
350  JS_ReportError(cx, "Invalid parameter to SetPaused");
351  }
352 
353  JS_SET_RVAL(cx, vp, JSVAL_VOID);
354  return JS_TRUE;
355 }
356 
357 
358 //-----------------------------------------------------------------------------
359 // function table
360 //-----------------------------------------------------------------------------
361 
362 // the JS interpreter expects the table to contain 5-tuples as follows:
363 // - name the function will be called as from script;
364 // - function which will be called;
365 // - number of arguments this function expects
366 // - Flags (deprecated, always zero)
367 // - Extra (reserved for future use, always zero)
368 //
369 // we simplify this a bit with a macro:
370 #define JS_FUNC(script_name, cpp_function, min_params) { script_name, cpp_function, min_params, 0 },
371 
372 JSFunctionSpec ScriptFunctionTable[] =
373 {
374  // Profiling
375  JS_FUNC("startXTimer", StartJsTimer, 1)
376  JS_FUNC("stopXTimer", StopJsTimer, 1)
377 
378  // Game Setup
379  JS_FUNC("endGame", EndGame, 0)
380 
381  // VFS (external)
382  JS_FUNC("buildDirEntList", JSI_VFS::BuildDirEntList, 1)
383  JS_FUNC("getFileMTime", JSI_VFS::GetFileMTime, 1)
384  JS_FUNC("getFileSize", JSI_VFS::GetFileSize, 1)
385  JS_FUNC("readFile", JSI_VFS::ReadFile, 1)
386  JS_FUNC("readFileLines", JSI_VFS::ReadFileLines, 1)
387  JS_FUNC("archiveBuilderCancel", JSI_VFS::ArchiveBuilderCancel, 1)
388 
389  // Misc. Engine Interface
390  JS_FUNC("exit", ExitProgram, 0)
391  JS_FUNC("isPaused", IsPaused, 0)
392  JS_FUNC("setPaused", SetPaused, 1)
393  JS_FUNC("setCursor", SetCursor, 1)
394  JS_FUNC("getFPS", GetFps, 0)
395  JS_FUNC("getGUIObjectByName", GetGUIObjectByName, 1)
396 
397  // Miscellany
398  JS_FUNC("buildTime", GetBuildTimestamp, 0)
399  JS_FUNC("dumpHeaps", DumpHeaps, 0)
400 
401  // end of table marker
402  {0}
403 };
404 #undef JS_FUNC
static TimerClient js_timer_clients[MAX_JS_TIMERS]
Definition: ScriptGlue.cpp:79
JSBool SetCursor(JSContext *cx, uintN argc, jsval *vp)
Definition: ScriptGlue.cpp:203
JSBool GetFps(JSContext *cx, uintN argc, jsval *vp)
Definition: ScriptGlue.cpp:170
#define JSU_REQUIRE_NO_PARAMS()
Definition: JSUtil.h:30
virtual void Pause(bool pauseIt)=0
const OsPath & psLogDir()
Definition: Pyrogenesis.cpp:96
CStrW g_CursorName
Definition: Config.cpp:31
ISoundManager * g_SoundManager
TimerClient * timer_AddClient(TimerClient *tc, const wchar_t *description)
make the given TimerClient (usually instantiated as static data) ready for use.
Definition: timer.cpp:166
wchar_t svn_revision[]
bool ToPrimitive< bool >(JSContext *cx, jsval v, bool &Storage)
Base settings, all objects possess these settings in their m_BaseSettings Instructions can be found i...
Definition: IGUIObject.h:140
void EndGame()
Definition: GameSetup.cpp:673
JSBool DumpHeaps(JSContext *cx, uintN argc, jsval *vp)
Definition: ScriptGlue.cpp:291
int swprintf_s(wchar_t *buf, size_t max_chars, const wchar_t *fmt,...) WPRINTF_ARGS(3)
CGUIManager * g_GUI
Definition: GUIManager.cpp:32
bill the difference between t0 and t1 to the client&#39;s total.
Definition: timer.h:315
int sprintf_s(char *buf, size_t max_chars, const char *fmt,...) PRINTF_ARGS(3)
static TimerUnit js_start_times[MAX_JS_TIMERS]
Definition: ScriptGlue.cpp:77
#define ARRAY_SIZE(name)
void kill_mainloop()
Definition: main.cpp:427
FILE * sys_OpenFile(const OsPath &pathname, const char *mode)
open a file like with fopen (but taking an OsPath argument).
Definition: unix.cpp:373
Hotkey system.
JSBool GetFileMTime(JSContext *cx, uintN argc, jsval *vp)
static TimerUnit js_timer_overhead
Definition: ScriptGlue.cpp:78
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
#define UNUSED2(param)
mark a function local variable or parameter as unused and avoid the corresponding compiler warning...
#define g_ScriptingHost
JSBool IsPaused(JSContext *cx, uintN argc, jsval *vp)
Definition: ScriptGlue.cpp:316
Definition: path.h:75
IGUIObject * FindObjectByName(const CStr &name) const
See CGUI::FindObjectByName; applies to the currently active page.
Definition: GUIManager.cpp:246
JSBool StartJsTimer(JSContext *cx, uintN argc, jsval *vp)
Definition: ScriptGlue.cpp:109
#define ONCE(ONCE_code__)
execute the code passed as a parameter only the first time this is reached.
bool m_Paused
the game is paused and no updates will be performed if true.
Definition: Game.h:74
ScriptInterface & GetScriptInterface() const
CGame * g_Game
Globally accessible pointer to the CGame object.
Definition: Game.cpp:56
TimerUnit sum
Definition: timer.h:276
static bool IsInitialised()
Definition: Singleton.h:63
static void InitJsTimers()
Definition: ScriptGlue.cpp:82
#define JSU_REQUIRE_PARAMS(exact_number)
Definition: JSUtil.h:26
PIFrequencyFilter g_frequencyFilter
Definition: Globals.cpp:37
void SetFromTimer()
Definition: timer.h:225
JSBool ExitProgram(JSContext *cx, uintN argc, jsval *vp)
Definition: ScriptGlue.cpp:187
CSimulation2 * GetSimulation2()
Get the pointer to the simulation2 object.
Definition: Game.h:136
JSBool GetGUIObjectByName(JSContext *cx, uintN argc, jsval *vp)
Definition: ScriptGlue.cpp:212
JSFunctionSpec ScriptFunctionTable[]
Definition: ScriptGlue.cpp:372
JSBool ReadFile(JSContext *cx, uintN argc, jsval *vp)
JSBool StopJsTimer(JSContext *cx, uintN argc, jsval *vp)
Definition: ScriptGlue.cpp:125
static const size_t MAX_JS_TIMERS
Definition: ScriptGlue.cpp:76
JSBool GetBuildTimestamp(JSContext *cx, uintN argc, jsval *vp)
Definition: ScriptGlue.cpp:250
#define JSU_REQUIRE_MAX_PARAMS(max_number)
Definition: JSUtil.h:38
JSBool BuildDirEntList(JSContext *cx, uintN argc, jsval *vp)
JSBool SetPaused(JSContext *cx, uintN argc, jsval *vp)
Definition: ScriptGlue.cpp:331
void Subtract(TimerUnit t)
Definition: timer.h:249
JSBool ReadFileLines(JSContext *cx, uintN argc, jsval *vp)
#define debug_warn(expr)
display the error dialog with the given text.
Definition: debug.h:324
void SetToZero()
Definition: timer.h:220
JSBool ArchiveBuilderCancel(JSContext *cx, uintN argc, jsval *vp)
static wchar_t js_timer_descriptions_buf[MAX_JS_TIMERS *12]
Definition: ScriptGlue.cpp:80
JSContext * GetContext() const
JSBool GetFileSize(JSContext *cx, uintN argc, jsval *vp)
#define JS_FUNC(script_name, cpp_function, min_params)
Definition: ScriptGlue.cpp:370