Merge pull request #535 from adxeproject/ccvalue-int64-support

Add `CCValue` int64_t/uint64_t support
This commit is contained in:
halx99 2021-11-09 15:03:10 +08:00 committed by GitHub
commit 382f33a704
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 203 additions and 69 deletions

View File

@ -27,6 +27,7 @@ adxe-1.0 ??
[HIGHLIGHT] Modularize all optional extension, move from engine core to folder extensions [HIGHLIGHT] Modularize all optional extension, move from engine core to folder extensions
[HIGHLIGHT] Improve thirdparty libs building, 99% of them build from sources or github actions with latest toolchain, see also: `adxeproject/buildware` [HIGHLIGHT] Improve thirdparty libs building, 99% of them build from sources or github actions with latest toolchain, see also: `adxeproject/buildware`
[HIGHLIGHT] Add new API `Director::setChildrenIndexerEnabled` for speed up getChildByTag & getChildByName support [HIGHLIGHT] Add new API `Director::setChildrenIndexerEnabled` for speed up getChildByTag & getChildByName support
[NEW] Add `CCValue` int64_t/uint64_t support
[FIX] Fix uniform location mismatch when more than 1 spine with different shaders [FIX] Fix uniform location mismatch when more than 1 spine with different shaders
[FIX] Fix imgui draw frame cause afterimage when game scene nothing to draw [FIX] Fix imgui draw frame cause afterimage when game scene nothing to draw
[FIX] Set global Z value of label debug layer to be the same as the parent label to fix display issue [FIX] Set global Z value of label debug layer to be the same as the parent label to fix display issue

View File

@ -45,21 +45,33 @@ Value::Value() : _type(Type::NONE)
memset(&_field, 0, sizeof(_field)); memset(&_field, 0, sizeof(_field));
} }
Value::Value(unsigned char v) : _type(Type::INTEGER) Value::Value(unsigned char v) : _type(Type::INT_UI32)
{ {
_field.uintVal = v; _field.uintVal = v;
} }
Value::Value(int v) : _type(Type::INTEGER) Value::Value(int v) : _type(Type::INT_I32)
{ {
_field.intVal = v; _field.intVal = v;
} }
Value::Value(unsigned int v) : _type(Type::UNSIGNED) Value::Value(unsigned int v) : _type(Type::INT_UI32)
{ {
_field.uintVal = v; _field.uintVal = v;
} }
/** Create a Value by an integer value. */
Value::Value(int64_t v) : _type(Type::INT_I64)
{
_field.int64Val = v;
}
/** Create a Value by an integer value. */
Value::Value(uint64_t v) : _type(Type::INT_UI64)
{
_field.uint64Val = v;
}
Value::Value(float v) : _type(Type::FLOAT) Value::Value(float v) : _type(Type::FLOAT)
{ {
_field.floatVal = v; _field.floatVal = v;
@ -141,13 +153,10 @@ Value& Value::operator=(const Value& other)
{ {
reset(other._type); reset(other._type);
switch (other._type) switch (other.getTypeFamily())
{ {
case Type::INTEGER: case Type::INTEGER:
_field.intVal = other._field.intVal; _field.uint64Val = other._field.uint64Val;
break;
case Type::UNSIGNED:
_field.uintVal = other._field.uintVal;
break; break;
case Type::FLOAT: case Type::FLOAT:
_field.floatVal = other._field.floatVal; _field.floatVal = other._field.floatVal;
@ -198,12 +207,9 @@ Value& Value::operator=(Value&& other)
if (this != &other) if (this != &other)
{ {
clear(); clear();
switch (other._type) switch (other.getTypeFamily())
{ {
case Type::INTEGER: case Type::INTEGER:
_field.intVal = other._field.intVal;
break;
case Type::UNSIGNED:
_field.uintVal = other._field.uintVal; _field.uintVal = other._field.uintVal;
break; break;
case Type::FLOAT: case Type::FLOAT:
@ -241,25 +247,39 @@ Value& Value::operator=(Value&& other)
Value& Value::operator=(unsigned char v) Value& Value::operator=(unsigned char v)
{ {
reset(Type::UNSIGNED); reset(Type::INT_UI32);
_field.uintVal = v; _field.uintVal = v;
return *this; return *this;
} }
Value& Value::operator=(int v) Value& Value::operator=(int v)
{ {
reset(Type::INTEGER); reset(Type::INT_I32);
_field.intVal = v; _field.intVal = v;
return *this; return *this;
} }
Value& Value::operator=(unsigned int v) Value& Value::operator=(unsigned int v)
{ {
reset(Type::UNSIGNED); reset(Type::INT_UI32);
_field.uintVal = v; _field.uintVal = v;
return *this; return *this;
} }
Value& Value::operator=(int64_t v)
{
reset(Type::INT_I64);
_field.int64Val = v;
return *this;
}
Value& Value::operator=(uint64_t v)
{
reset(Type::INT_UI64);
_field.uint64Val = v;
return *this;
}
Value& Value::operator=(float v) Value& Value::operator=(float v)
{ {
reset(Type::FLOAT); reset(Type::FLOAT);
@ -366,12 +386,16 @@ bool Value::operator==(const Value& v) const
return false; return false;
if (this->isNull()) if (this->isNull())
return true; return true;
switch (_type) switch (getType())
{ {
case Type::INTEGER: case Type::INT_I32:
return v._field.intVal == this->_field.intVal; return v._field.intVal == this->_field.intVal;
case Type::UNSIGNED: case Type::INT_UI32:
return v._field.uintVal == this->_field.uintVal; return v._field.uintVal == this->_field.uintVal;
case Type::INT_I64:
return v._field.int64Val == this->_field.int64Val;
case Type::INT_UI64:
return v._field.int64Val == this->_field.int64Val;
case Type::BOOLEAN: case Type::BOOLEAN:
return v._field.boolVal == this->_field.boolVal; return v._field.boolVal == this->_field.boolVal;
case Type::STRING: case Type::STRING:
@ -439,11 +463,14 @@ unsigned char Value::asByte(unsigned char defaultValue) const
switch (_type) switch (_type)
{ {
case Type::INTEGER: case Type::INT_UI32:
return static_cast<unsigned char>(_field.intVal);
case Type::UNSIGNED:
return static_cast<unsigned char>(_field.uintVal); return static_cast<unsigned char>(_field.uintVal);
case Type::INT_I32:
return static_cast<unsigned char>(_field.intVal);
case Type::INT_I64:
return static_cast<unsigned char>(_field.int64Val);
case Type::INT_UI64:
return static_cast<unsigned char>(_field.uint64Val);
case Type::STRING: case Type::STRING:
return static_cast<unsigned char>(atoi(_field.strVal->c_str())); return static_cast<unsigned char>(atoi(_field.strVal->c_str()));
@ -468,12 +495,14 @@ int Value::asInt(int defaultValue) const
"Only base type (bool, string, float, double, int) could be converted"); "Only base type (bool, string, float, double, int) could be converted");
switch (_type) switch (_type)
{ {
case Type::INTEGER: case Type::INT_I32:
return _field.intVal; return (_field.intVal);
case Type::INT_UI32:
case Type::UNSIGNED: return static_cast<int>(_field.uintVal);
CCASSERT(_field.uintVal < INT_MAX, "Can only convert values < INT_MAX"); case Type::INT_I64:
return (int)_field.uintVal; return static_cast<int>(_field.int64Val);
case Type::INT_UI64:
return static_cast<int>(_field.uint64Val);
case Type::STRING: case Type::STRING:
return atoi(_field.strVal->c_str()); return atoi(_field.strVal->c_str());
@ -492,18 +521,20 @@ int Value::asInt(int defaultValue) const
} }
} }
unsigned int Value::asUnsignedInt(unsigned int defaultValue) const unsigned int Value::asUint(unsigned int defaultValue) const
{ {
CCASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP, CCASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP,
"Only base type (bool, string, float, double, int) could be converted"); "Only base type (bool, string, float, double, int) could be converted");
switch (_type) switch (_type)
{ {
case Type::UNSIGNED: case Type::INT_UI32:
return _field.uintVal; return (_field.uintVal);
case Type::INT_I32:
case Type::INTEGER:
CCASSERT(_field.intVal >= 0, "Only values >= 0 can be converted to unsigned");
return static_cast<unsigned int>(_field.intVal); return static_cast<unsigned int>(_field.intVal);
case Type::INT_I64:
return static_cast<unsigned int>(_field.int64Val);
case Type::INT_UI64:
return static_cast<unsigned int>(_field.uint64Val);
case Type::STRING: case Type::STRING:
// NOTE: strtoul is required (need to augment on unsupported platforms) // NOTE: strtoul is required (need to augment on unsupported platforms)
@ -523,6 +554,72 @@ unsigned int Value::asUnsignedInt(unsigned int defaultValue) const
} }
} }
int64_t Value::asInt64(int64_t defaultValue) const
{
CCASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP,
"Only base type (bool, string, float, double, int) could be converted");
switch (_type)
{
case Type::INT_I64:
return (_field.int64Val);
case Type::INT_I32:
return static_cast<int64_t>(_field.intVal);
case Type::INT_UI32:
return static_cast<int64_t>(_field.uintVal);
case Type::INT_UI64:
return static_cast<int64_t>(_field.uint64Val);
case Type::STRING:
// NOTE: strtoul is required (need to augment on unsupported platforms)
return static_cast<int64_t>(strtoul(_field.strVal->c_str(), nullptr, 10));
case Type::FLOAT:
return static_cast<int64_t>(_field.floatVal);
case Type::DOUBLE:
return static_cast<int64_t>(_field.doubleVal);
case Type::BOOLEAN:
return _field.boolVal ? 1u : 0u;
default:
return defaultValue;
}
}
uint64_t Value::asUint64(uint64_t defaultValue) const
{
CCASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP,
"Only base type (bool, string, float, double, int) could be converted");
switch (_type)
{
case Type::INT_UI64:
return (_field.uint64Val);
case Type::INT_I32:
return static_cast<uint64_t>(_field.intVal);
case Type::INT_UI32:
return static_cast<uint64_t>(_field.uintVal);
case Type::INT_I64:
return static_cast<uint64_t>(_field.int64Val);
case Type::STRING:
// NOTE: strtoul is required (need to augment on unsupported platforms)
return static_cast<uint64_t>(strtoull(_field.strVal->c_str(), nullptr, 10));
case Type::FLOAT:
return static_cast<uint64_t>(_field.floatVal);
case Type::DOUBLE:
return static_cast<uint64_t>(_field.doubleVal);
case Type::BOOLEAN:
return _field.boolVal ? 1u : 0u;
default:
return defaultValue;
}
}
float Value::asFloat(float defaultValue) const float Value::asFloat(float defaultValue) const
{ {
CCASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP, CCASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP,
@ -535,11 +632,14 @@ float Value::asFloat(float defaultValue) const
case Type::STRING: case Type::STRING:
return static_cast<float>(utils::atof(_field.strVal->c_str())); return static_cast<float>(utils::atof(_field.strVal->c_str()));
case Type::INTEGER: case Type::INT_I32:
return static_cast<float>(_field.intVal); return static_cast<float>(_field.intVal);
case Type::INT_UI32:
case Type::UNSIGNED:
return static_cast<float>(_field.uintVal); return static_cast<float>(_field.uintVal);
case Type::INT_I64:
return static_cast<float>(_field.int64Val);
case Type::INT_UI64:
return static_cast<float>(_field.uint64Val);
case Type::DOUBLE: case Type::DOUBLE:
return static_cast<float>(_field.doubleVal); return static_cast<float>(_field.doubleVal);
@ -564,11 +664,14 @@ double Value::asDouble(double defaultValue) const
case Type::STRING: case Type::STRING:
return static_cast<double>(utils::atof(_field.strVal->c_str())); return static_cast<double>(utils::atof(_field.strVal->c_str()));
case Type::INTEGER: case Type::INT_I32:
return static_cast<double>(_field.intVal); return static_cast<double>(_field.intVal);
case Type::INT_UI32:
case Type::UNSIGNED:
return static_cast<double>(_field.uintVal); return static_cast<double>(_field.uintVal);
case Type::INT_I64:
return static_cast<double>(_field.int64Val);
case Type::INT_UI64:
return static_cast<double>(_field.uint64Val);
case Type::FLOAT: case Type::FLOAT:
return static_cast<double>(_field.floatVal); return static_cast<double>(_field.floatVal);
@ -593,11 +696,14 @@ bool Value::asBool(bool defaultValue) const
case Type::STRING: case Type::STRING:
return (*_field.strVal == "0" || *_field.strVal == "false") ? false : true; return (*_field.strVal == "0" || *_field.strVal == "false") ? false : true;
case Type::INTEGER: case Type::INT_I32:
return _field.intVal == 0 ? false : true; return !!_field.intVal;
case Type::INT_UI32:
case Type::UNSIGNED: return !!_field.uintVal;
return _field.uintVal == 0 ? false : true; case Type::INT_I64:
return !!_field.int64Val;
case Type::INT_UI64:
return !!_field.uint64Val;
case Type::FLOAT: case Type::FLOAT:
return _field.floatVal == 0.0f ? false : true; return _field.floatVal == 0.0f ? false : true;
@ -629,12 +735,18 @@ std::string Value::asString() const
size_t n = 0; size_t n = 0;
switch (_type) switch (_type)
{ {
case Type::INTEGER: case Type::INT_I32:
ret = std::to_string(_field.intVal); ret = std::to_string(_field.intVal);
break; break;
case Type::UNSIGNED: case Type::INT_UI32:
ret = std::to_string(_field.uintVal); ret = std::to_string(_field.uintVal);
break; break;
case Type::INT_I64:
ret = std::to_string(_field.int64Val);
break;
case Type::INT_UI64:
ret = std::to_string(_field.uint64Val);
break;
case Type::FLOAT: case Type::FLOAT:
ret.resize(NUMBER_MAX_DIGITS); ret.resize(NUMBER_MAX_DIGITS);
n = snprintf(&ret.front(), NUMBER_MAX_DIGITS + 1, "%.*g", 7 /*precision*/, _field.floatVal); n = snprintf(&ret.front(), NUMBER_MAX_DIGITS + 1, "%.*g", 7 /*precision*/, _field.floatVal);
@ -760,11 +872,10 @@ static std::string visit(const Value& v, int depth)
{ {
std::stringstream ret; std::stringstream ret;
switch (v.getType()) switch (v.getTypeFamily())
{ {
case Value::Type::NONE: case Value::Type::NONE:
case Value::Type::INTEGER: case Value::Type::INTEGER:
case Value::Type::UNSIGNED:
case Value::Type::FLOAT: case Value::Type::FLOAT:
case Value::Type::DOUBLE: case Value::Type::DOUBLE:
case Value::Type::BOOLEAN: case Value::Type::BOOLEAN:
@ -798,13 +909,10 @@ std::string Value::getDescription() const
void Value::clear() void Value::clear()
{ {
// Free memory the old value allocated // Free memory the old value allocated
switch (_type) switch (getTypeFamily())
{ {
case Type::INTEGER: case Type::INTEGER:
_field.intVal = 0; _field.uint64Val = 0;
break;
case Type::UNSIGNED:
_field.uintVal = 0u;
break; break;
case Type::FLOAT: case Type::FLOAT:
_field.floatVal = 0.0f; _field.floatVal = 0.0f;

View File

@ -72,6 +72,12 @@ public:
/** Create a Value by an unsigned value. */ /** Create a Value by an unsigned value. */
explicit Value(unsigned int v); explicit Value(unsigned int v);
/** Create a Value by an integer value. */
explicit Value(int64_t v);
/** Create a Value by an integer value. */
explicit Value(uint64_t v);
/** Create a Value by a float value. */ /** Create a Value by a float value. */
explicit Value(float v); explicit Value(float v);
@ -123,6 +129,10 @@ public:
Value& operator= (int v); Value& operator= (int v);
/** Assignment operator, assign from integer to Value. */ /** Assignment operator, assign from integer to Value. */
Value& operator= (unsigned int v); Value& operator= (unsigned int v);
/** Assignment operator, assign from integer to Value. */
Value& operator=(int64_t v);
/** Assignment operator, assign from integer to Value. */
Value& operator=(uint64_t v);
/** Assignment operator, assign from float to Value. */ /** Assignment operator, assign from float to Value. */
Value& operator= (float v); Value& operator= (float v);
/** Assignment operator, assign from double to Value. */ /** Assignment operator, assign from double to Value. */
@ -164,7 +174,12 @@ public:
/** Gets as an integer value. Will convert to integer if possible, or will trigger assert error. */ /** Gets as an integer value. Will convert to integer if possible, or will trigger assert error. */
int asInt(int defaultValue = 0) const; int asInt(int defaultValue = 0) const;
/** Gets as an unsigned value. Will convert to unsigned if possible, or will trigger assert error. */ /** Gets as an unsigned value. Will convert to unsigned if possible, or will trigger assert error. */
unsigned int asUnsignedInt(unsigned defaultValue = 0) const; unsigned int asUint(unsigned int defaultValue = 0) const;
unsigned int asUnsignedInt(unsigned int defaultValue = 0) const { return asUint(defaultValue); }
/** Gets as an integer value. Will convert to integer if possible, or will trigger assert error. */
int64_t asInt64(int64_t defaultValue = 0) const;
/** Gets as an unsigned value. Will convert to unsigned if possible, or will trigger assert error. */
uint64_t asUint64(uint64_t defaultValue = 0) const;
/** Gets as a float value. Will convert to float if possible, or will trigger assert error. */ /** Gets as a float value. Will convert to float if possible, or will trigger assert error. */
float asFloat(float defaultValue = 0.0f) const; float asFloat(float defaultValue = 0.0f) const;
/** Gets as a double value. Will convert to double if possible, or will trigger assert error. */ /** Gets as a double value. Will convert to double if possible, or will trigger assert error. */
@ -199,14 +214,12 @@ public:
bool isNull() const { return _type == Type::NONE; } bool isNull() const { return _type == Type::NONE; }
/** Value type wrapped by Value. */ /** Value type wrapped by Value. */
enum class Type enum class Type : uint32_t
{ {
/// no value is wrapped, an empty Value /// no value is wrapped, an empty Value
NONE = 0, NONE = 0,
/// wrap integer /// wrap integer
INTEGER, INTEGER,
/// wrap unsigned
UNSIGNED,
/// wrap float /// wrap float
FLOAT, FLOAT,
/// wrap double /// wrap double
@ -220,12 +233,22 @@ public:
/// wrap ValueMap /// wrap ValueMap
MAP, MAP,
/// wrap ValueMapIntKey /// wrap ValueMapIntKey
INT_KEY_MAP INT_KEY_MAP,
MASK_UNSIGNED = 1 << 17,
MASK_64BIT = 1 << 18,
INT_I32 = INTEGER,
INT_UI32 = INTEGER | MASK_UNSIGNED,
INT_I64 = INTEGER | MASK_64BIT,
INT_UI64 = INTEGER | MASK_64BIT | MASK_UNSIGNED,
}; };
/** Gets the value type. */ /** Gets the value type. */
Type getType() const { return _type; } Type getType() const { return _type; }
/** Gets the value type family. */
Type getTypeFamily() const { return (Type)((uint32_t)_type & 0xFFFFu); }
/** Gets the description of the class. */ /** Gets the description of the class. */
std::string getDescription() const; std::string getDescription() const;
@ -237,6 +260,8 @@ private:
{ {
int intVal; int intVal;
unsigned int uintVal; unsigned int uintVal;
int64_t int64Val;
uint64_t uint64Val;
float floatVal; float floatVal;
double doubleVal; double doubleVal;
bool boolVal; bool boolVal;

View File

@ -2344,7 +2344,7 @@ void fontdefinition_to_luaval(lua_State* L,const FontDefinition& inValue)
void ccvalue_to_luaval(lua_State* L,const cocos2d::Value& inValue) void ccvalue_to_luaval(lua_State* L,const cocos2d::Value& inValue)
{ {
const Value& obj = inValue; const Value& obj = inValue;
switch (obj.getType()) switch (obj.getTypeFamily())
{ {
case Value::Type::BOOLEAN: case Value::Type::BOOLEAN:
lua_pushboolean(L, obj.asBool()); lua_pushboolean(L, obj.asBool());
@ -2354,7 +2354,7 @@ void ccvalue_to_luaval(lua_State* L,const cocos2d::Value& inValue)
lua_pushnumber(L, obj.asDouble()); lua_pushnumber(L, obj.asDouble());
break; break;
case Value::Type::INTEGER: case Value::Type::INTEGER:
lua_pushinteger(L, obj.asInt()); lua_pushinteger(L, obj.asInt64());
break; break;
case Value::Type::STRING: case Value::Type::STRING:
lua_pushstring(L, obj.asStringRef().c_str()); lua_pushstring(L, obj.asStringRef().c_str());
@ -2383,7 +2383,7 @@ void ccvaluemap_to_luaval(lua_State* L,const cocos2d::ValueMap& inValue)
{ {
std::string key = iter->first; std::string key = iter->first;
const Value& obj = iter->second; const Value& obj = iter->second;
switch (obj.getType()) switch (obj.getTypeFamily())
{ {
case Value::Type::BOOLEAN: case Value::Type::BOOLEAN:
{ {
@ -2403,7 +2403,7 @@ void ccvaluemap_to_luaval(lua_State* L,const cocos2d::ValueMap& inValue)
case Value::Type::INTEGER: case Value::Type::INTEGER:
{ {
lua_pushstring(L, key.c_str()); lua_pushstring(L, key.c_str());
lua_pushinteger(L, obj.asInt()); lua_pushinteger(L, obj.asInt64());
lua_rawset(L, -3); lua_rawset(L, -3);
} }
break; break;
@ -2455,7 +2455,7 @@ void ccvaluemapintkey_to_luaval(lua_State* L, const cocos2d::ValueMapIntKey& inV
const Value& obj = iter->second; const Value& obj = iter->second;
switch (obj.getType()) switch (obj.getTypeFamily())
{ {
case Value::Type::BOOLEAN: case Value::Type::BOOLEAN:
{ {
@ -2475,7 +2475,7 @@ void ccvaluemapintkey_to_luaval(lua_State* L, const cocos2d::ValueMapIntKey& inV
case Value::Type::INTEGER: case Value::Type::INTEGER:
{ {
lua_pushstring(L, key.c_str()); lua_pushstring(L, key.c_str());
lua_pushinteger(L, obj.asInt()); lua_pushinteger(L, obj.asInt64());
lua_rawset(L, -3); lua_rawset(L, -3);
} }
break; break;
@ -2522,7 +2522,7 @@ void ccvaluevector_to_luaval(lua_State* L, const cocos2d::ValueVector& inValue)
int index = 1; int index = 1;
for (const auto& obj : inValue) for (const auto& obj : inValue)
{ {
switch (obj.getType()) switch (obj.getTypeFamily())
{ {
case Value::Type::BOOLEAN: case Value::Type::BOOLEAN:
{ {
@ -2544,7 +2544,7 @@ void ccvaluevector_to_luaval(lua_State* L, const cocos2d::ValueVector& inValue)
case Value::Type::INTEGER: case Value::Type::INTEGER:
{ {
lua_pushnumber(L, (lua_Number)index); lua_pushnumber(L, (lua_Number)index);
lua_pushnumber(L, obj.asInt()); lua_pushnumber(L, obj.asInt64());
lua_rawset(L, -3); lua_rawset(L, -3);
++index; ++index;
} }

View File

@ -322,12 +322,12 @@ ValueTypeJudgeInTable* ValueTypeJudgeInTable::create(ValueMap valueMap)
int index = 0; int index = 0;
for (const auto& iter : valueMap) for (const auto& iter : valueMap)
{ {
Value::Type type = iter.second.getType(); Value::Type type = iter.second.getTypeFamily();
if (type == Value::Type::STRING) { if (type == Value::Type::STRING) {
CCLOG("The type of index %d is string", index); CCLOG("The type of index %d is string", index);
} }
if (type == Value::Type::INTEGER || type == Value::Type::DOUBLE || type == Value::Type::FLOAT || type == Value::Type::BYTE) { if (type == Value::Type::INTEGER || type == Value::Type::DOUBLE || type == Value::Type::FLOAT) {
CCLOG("The type of index %d is number", index); CCLOG("The type of index %d is number", index);
} }