1 /* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "scriptinterface/Object.h"
21 #include "scriptinterface/ScriptConversions.h"
22 #include "scriptinterface/ScriptInterface.h"
24 #include "graphics/Color.h"
25 #include "maths/Fixed.h"
26 #include "maths/FixedVector2D.h"
27 #include "maths/FixedVector3D.h"
28 #include "maths/Rect.h"
29 #include "ps/CLogger.h"
30 #include "simulation2/helpers/CinemaPath.h"
31 #include "simulation2/helpers/Grid.h"
32 #include "simulation2/system/IComponent.h"
33 #include "simulation2/system/ParamNode.h"
35 #define FAIL(msg) STMT(LOGERROR(msg); return false)
36 #define FAIL_VOID(msg) STMT(ScriptException::Raise(rq, msg); return)
38 template<> void Script::ToJSVal
<IComponent
*>(const ScriptRequest
& rq
, JS::MutableHandleValue ret
, IComponent
* const& val
)
46 // If this is a scripted component, just return the JS object directly
47 JS::RootedValue
instance(rq
.cx
, val
->GetJSInstance());
48 if (!instance
.isNull())
54 // Otherwise we need to construct a wrapper object
55 // (TODO: cache wrapper objects?)
56 JS::RootedObject
obj(rq
.cx
);
57 if (!val
->NewJSObject(rq
.GetScriptInterface(), &obj
))
59 // Report as an error, since scripts really shouldn't try to use unscriptable interfaces
60 LOGERROR("IComponent does not have a scriptable interface");
65 JS::SetPrivate(obj
, static_cast<void*>(val
));
69 template<> void Script::ToJSVal
<CParamNode
>(const ScriptRequest
& rq
, JS::MutableHandleValue ret
, CParamNode
const& val
)
71 val
.ToJSVal(rq
, true, ret
);
73 // Prevent modifications to the object, so that it's safe to share between
74 // components and to reconstruct on deserialization
76 Script::FreezeObject(rq
, ret
, true);
79 template<> void Script::ToJSVal
<const CParamNode
*>(const ScriptRequest
& rq
, JS::MutableHandleValue ret
, const CParamNode
* const& val
)
82 ToJSVal(rq
, ret
, *val
);
87 template<> bool Script::FromJSVal
<CColor
>(const ScriptRequest
& rq
, JS::HandleValue v
, CColor
& out
)
90 FAIL("CColor has to be an object");
92 JS::RootedObject
obj(rq
.cx
, &v
.toObject());
94 JS::RootedValue
r(rq
.cx
);
95 JS::RootedValue
g(rq
.cx
);
96 JS::RootedValue
b(rq
.cx
);
97 JS::RootedValue
a(rq
.cx
);
98 if (!JS_GetProperty(rq
.cx
, obj
, "r", &r
) || !FromJSVal(rq
, r
, out
.r
))
99 FAIL("Failed to get property CColor.r");
100 if (!JS_GetProperty(rq
.cx
, obj
, "g", &g
) || !FromJSVal(rq
, g
, out
.g
))
101 FAIL("Failed to get property CColor.g");
102 if (!JS_GetProperty(rq
.cx
, obj
, "b", &b
) || !FromJSVal(rq
, b
, out
.b
))
103 FAIL("Failed to get property CColor.b");
104 if (!JS_GetProperty(rq
.cx
, obj
, "a", &a
) || !FromJSVal(rq
, a
, out
.a
))
105 FAIL("Failed to get property CColor.a");
110 template<> void Script::ToJSVal
<CColor
>(const ScriptRequest
& rq
, JS::MutableHandleValue ret
, CColor
const& val
)
112 Script::CreateObject(
121 template<> bool Script::FromJSVal
<fixed
>(const ScriptRequest
& rq
, JS::HandleValue v
, fixed
& out
)
124 if (!JS::ToNumber(rq
.cx
, v
, &ret
))
126 out
= fixed::FromDouble(ret
);
127 // double can precisely represent the full range of fixed, so this is a non-lossy conversion
132 template<> void Script::ToJSVal
<fixed
>(const ScriptRequest
& UNUSED(rq
), JS::MutableHandleValue ret
, const fixed
& val
)
134 ret
.set(JS::NumberValue(val
.ToDouble()));
137 template<> bool Script::FromJSVal
<CFixedVector3D
>(const ScriptRequest
& rq
, JS::HandleValue v
, CFixedVector3D
& out
)
140 return false; // TODO: report type error
142 JS::RootedObject
obj(rq
.cx
, &v
.toObject());
143 JS::RootedValue
p(rq
.cx
);
145 if (!JS_GetProperty(rq
.cx
, obj
, "x", &p
)) return false; // TODO: report type errors
146 if (!FromJSVal(rq
, p
, out
.X
)) return false;
148 if (!JS_GetProperty(rq
.cx
, obj
, "y", &p
)) return false;
149 if (!FromJSVal(rq
, p
, out
.Y
)) return false;
151 if (!JS_GetProperty(rq
.cx
, obj
, "z", &p
)) return false;
152 if (!FromJSVal(rq
, p
, out
.Z
)) return false;
157 template<> void Script::ToJSVal
<CFixedVector3D
>(const ScriptRequest
& rq
, JS::MutableHandleValue ret
, const CFixedVector3D
& val
)
159 JS::RootedObject
global(rq
.cx
, rq
.glob
);
160 JS::RootedValue
valueVector3D(rq
.cx
);
161 if (!ScriptInterface::GetGlobalProperty(rq
, "Vector3D", &valueVector3D
))
162 FAIL_VOID("Failed to get Vector3D constructor");
164 JS::RootedValueArray
<3> args(rq
.cx
);
165 args
[0].setNumber(val
.X
.ToDouble());
166 args
[1].setNumber(val
.Y
.ToDouble());
167 args
[2].setNumber(val
.Z
.ToDouble());
169 JS::RootedObject
objVec(rq
.cx
);
170 if (!JS::Construct(rq
.cx
, valueVector3D
, args
, &objVec
))
171 FAIL_VOID("Failed to construct Vector3D object");
173 ret
.setObject(*objVec
);
176 template<> bool Script::FromJSVal
<CFixedVector2D
>(const ScriptRequest
& rq
, JS::HandleValue v
, CFixedVector2D
& out
)
179 return false; // TODO: report type error
180 JS::RootedObject
obj(rq
.cx
, &v
.toObject());
182 JS::RootedValue
p(rq
.cx
);
184 if (!JS_GetProperty(rq
.cx
, obj
, "x", &p
)) return false; // TODO: report type errors
185 if (!FromJSVal(rq
, p
, out
.X
)) return false;
187 if (!JS_GetProperty(rq
.cx
, obj
, "y", &p
)) return false;
188 if (!FromJSVal(rq
, p
, out
.Y
)) return false;
193 template<> void Script::ToJSVal
<CFixedVector2D
>(const ScriptRequest
& rq
, JS::MutableHandleValue ret
, const CFixedVector2D
& val
)
195 JS::RootedObject
global(rq
.cx
, rq
.glob
);
196 JS::RootedValue
valueVector2D(rq
.cx
);
197 if (!ScriptInterface::GetGlobalProperty(rq
, "Vector2D", &valueVector2D
))
198 FAIL_VOID("Failed to get Vector2D constructor");
200 JS::RootedValueArray
<2> args(rq
.cx
);
201 args
[0].setNumber(val
.X
.ToDouble());
202 args
[1].setNumber(val
.Y
.ToDouble());
204 JS::RootedObject
objVec(rq
.cx
);
205 if (!JS::Construct(rq
.cx
, valueVector2D
, args
, &objVec
))
206 FAIL_VOID("Failed to construct Vector2D object");
208 ret
.setObject(*objVec
);
211 template<> void Script::ToJSVal
<Grid
<u8
> >(const ScriptRequest
& rq
, JS::MutableHandleValue ret
, const Grid
<u8
>& val
)
213 u32 length
= (u32
)(val
.m_W
* val
.m_H
);
214 u32 nbytes
= (u32
)(length
* sizeof(u8
));
215 JS::RootedObject
objArr(rq
.cx
, JS_NewUint8Array(rq
.cx
, length
));
216 // Copy the array data and then remove the no-GC check to allow further changes to the JS data
218 JS::AutoCheckCannotGC nogc
;
220 memcpy((void*)JS_GetUint8ArrayData(objArr
, &sharedMemory
, nogc
), val
.m_Data
, nbytes
);
223 JS::RootedValue
data(rq
.cx
, JS::ObjectValue(*objArr
));
224 Script::CreateObject(
232 template<> void Script::ToJSVal
<Grid
<u16
> >(const ScriptRequest
& rq
, JS::MutableHandleValue ret
, const Grid
<u16
>& val
)
234 u32 length
= (u32
)(val
.m_W
* val
.m_H
);
235 u32 nbytes
= (u32
)(length
* sizeof(u16
));
236 JS::RootedObject
objArr(rq
.cx
, JS_NewUint16Array(rq
.cx
, length
));
237 // Copy the array data and then remove the no-GC check to allow further changes to the JS data
239 JS::AutoCheckCannotGC nogc
;
241 memcpy((void*)JS_GetUint16ArrayData(objArr
, &sharedMemory
, nogc
), val
.m_Data
, nbytes
);
244 JS::RootedValue
data(rq
.cx
, JS::ObjectValue(*objArr
));
245 Script::CreateObject(
253 template<> bool Script::FromJSVal
<TNSpline
>(const ScriptRequest
& rq
, JS::HandleValue v
, TNSpline
& out
)
256 FAIL("Argument must be an object");
258 JS::RootedObject
obj(rq
.cx
, &v
.toObject());
260 if (!JS::IsArrayObject(rq
.cx
, obj
, &isArray
) || !isArray
)
261 FAIL("Argument must be an array");
263 u32 numberOfNodes
= 0;
264 if (!JS::GetArrayLength(rq
.cx
, obj
, &numberOfNodes
))
265 FAIL("Failed to get array length");
267 for (u32 i
= 0; i
< numberOfNodes
; ++i
)
269 JS::RootedValue
node(rq
.cx
);
270 if (!JS_GetElement(rq
.cx
, obj
, i
, &node
))
271 FAIL("Failed to read array element");
274 if (!FromJSProperty(rq
, node
, "deltaTime", deltaTime
))
275 FAIL("Failed to read Spline.deltaTime property");
277 CFixedVector3D position
;
278 if (!FromJSProperty(rq
, node
, "position", position
))
279 FAIL("Failed to read Spline.position property");
281 out
.AddNode(position
, CFixedVector3D(), deltaTime
);
284 if (out
.GetAllNodes().empty())
285 FAIL("Spline must contain at least one node");
290 template<> bool Script::FromJSVal
<CCinemaPath
>(const ScriptRequest
& rq
, JS::HandleValue v
, CCinemaPath
& out
)
293 FAIL("Argument must be an object");
295 JS::RootedObject
obj(rq
.cx
, &v
.toObject());
297 CCinemaData pathData
;
298 TNSpline positionSpline
, targetSpline
;
300 if (!FromJSProperty(rq
, v
, "name", pathData
.m_Name
))
301 FAIL("Failed to get CCinemaPath.name property");
303 if (!FromJSProperty(rq
, v
, "orientation", pathData
.m_Orientation
))
304 FAIL("Failed to get CCinemaPath.orientation property");
306 if (!FromJSProperty(rq
, v
, "positionNodes", positionSpline
))
307 FAIL("Failed to get CCinemaPath.positionNodes property");
309 if (pathData
.m_Orientation
== L
"target" && !FromJSProperty(rq
, v
, "targetNodes", targetSpline
))
310 FAIL("Failed to get CCinemaPath.targetNodes property");
312 // Other properties are not necessary to be defined
313 if (!FromJSProperty(rq
, v
, "timescale", pathData
.m_Timescale
))
314 pathData
.m_Timescale
= fixed::FromInt(1);
316 if (!FromJSProperty(rq
, v
, "mode", pathData
.m_Mode
))
317 pathData
.m_Mode
= L
"ease_inout";
319 if (!FromJSProperty(rq
, v
, "style", pathData
.m_Style
))
320 pathData
.m_Style
= L
"default";
322 out
= CCinemaPath(pathData
, positionSpline
, targetSpline
);
328 JSVAL_VECTOR(CFixedVector2D
)