aboutsummaryrefslogtreecommitdiff
path: root/src/intercopycontext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/intercopycontext.cpp')
-rw-r--r--src/intercopycontext.cpp298
1 files changed, 186 insertions, 112 deletions
diff --git a/src/intercopycontext.cpp b/src/intercopycontext.cpp
index 6e9b66c..a8b3374 100644
--- a/src/intercopycontext.cpp
+++ b/src/intercopycontext.cpp
@@ -513,72 +513,130 @@ void InterCopyContext::interCopyKeyValuePair() const
513 513
514// ################################################################################################# 514// #################################################################################################
515 515
516LuaType InterCopyContext::processConversion() const 516ConvertMode InterCopyContext::lookupConverter() const
517{ 517{
518 static constexpr int kPODmask = (1 << LUA_TNIL) | (1 << LUA_TBOOLEAN) | (1 << LUA_TLIGHTUSERDATA) | (1 << LUA_TNUMBER) | (1 << LUA_TSTRING); 518 static constexpr std::string_view kConvertField{ "__lanesconvert" };
519
520 LuaType _val_type{ luaW_type(L1, L1_i) };
521
522 STACK_CHECK_START_REL(L1, 0); 519 STACK_CHECK_START_REL(L1, 0);
523 520
524 // it's a POD: nothing to do 521 // lookup and push a converter on the stack
525 if (((1 << static_cast<int>(_val_type)) & kPODmask) != 0) { 522 LuaType const _convertType{
526 return _val_type; 523 std::invoke([this]() {
527 } 524 // never convert from inside a keeper
525 if (mode == LookupMode::FromKeeper) {
526 lua_pushnil(L1);
527 return LuaType::NIL;
528 }
529 if (lua_getmetatable(L1, L1_i)) { // L1: ... mt
530 // we have a metatable: grab the converter inside it
531 LuaType const _convertType{ luaW_getfield(L1, kIdxTop, kConvertField) }; // L1: ... mt <converter>
532 lua_remove(L1, -2); // L1: ... <converter>
533 return _convertType;
534 }
535 // no metatable: setup converter from the global settings
536 switch (U->convertMode) {
537 case ConvertMode::DoNothing:
538 lua_pushnil(L1);
539 return LuaType::NIL;
540
541 case ConvertMode::ConvertToNil:
542 kNilSentinel.pushKey(L1);
543 return LuaType::LIGHTUSERDATA;
544
545 case ConvertMode::Decay:
546 luaW_pushstring(L1, "decay");
547 return LuaType::STRING;
548
549 case ConvertMode::UserConversion:
550 raise_luaL_error(getErrL(), "INTERNAL ERROR: function-based conversion should have been prevented at configure settings validation");
551 }
552 // technically unreachable, since all cases are handled and raise_luaL_error() is [[noreturn]], but MSVC raises C4715 without that:
553 return LuaType::NIL;
554 })
555 };
556 STACK_CHECK(L1, 1);
528 557
529 // no metatable: nothing to do 558 ConvertMode _convertMode{ ConvertMode::DoNothing };
530 if (!lua_getmetatable(L1, L1_i)) { // L1: ... 559 LUA_ASSERT(getErrL(), luaW_type(L1, kIdxTop) == _convertType);
531 STACK_CHECK(L1, 0); 560 switch (_convertType) {
532 return _val_type; 561 case LuaType::NIL: // L1: ... nil
533 } 562 // no converter, nothing to do
534 // we have a metatable // L1: ... mt
535 static constexpr std::string_view kConvertField{ "__lanesconvert" };
536 LuaType const _converterType{ luaW_getfield(L1, kIdxTop, kConvertField) }; // L1: ... mt kConvertField
537 switch (_converterType) {
538 case LuaType::NIL:
539 // no __lanesconvert, nothing to do
540 lua_pop(L1, 2); // L1: ...
541 break; 563 break;
542 564
543 case LuaType::LIGHTUSERDATA: 565 case LuaType::LIGHTUSERDATA:
544 if (kNilSentinel.equals(L1, kIdxTop)) { 566 if (!kNilSentinel.equals(L1, kIdxTop)) {
545 DEBUGSPEW_CODE(DebugSpew(U) << "converted " << luaW_typename(L1, _val_type) << " to nil" << std::endl); 567 raise_luaL_error(getErrL(), "Invalid %s type %s", kConvertField.data(), luaW_typename(L1, _convertType).data());
546 lua_replace(L1, L1_i); // L1: ... mt
547 lua_pop(L1, 1); // L1: ...
548 _val_type = _converterType;
549 } else {
550 raise_luaL_error(getErrL(), "Invalid %s type %s", kConvertField.data(), luaW_typename(L1, _converterType).data());
551 } 568 }
569 _convertMode = ConvertMode::ConvertToNil;
552 break; 570 break;
553 571
554 case LuaType::STRING: 572 case LuaType::STRING:
555 // kConvertField == "decay" -> replace source value with it's pointer 573 if (std::string_view const _mode{ luaW_tostring(L1, kIdxTop) }; _mode != "decay") { // L1: ... "<some string>"
556 if (std::string_view const _mode{ luaW_tostring(L1, kIdxTop) }; _mode == "decay") {
557 lua_pop(L1, 1); // L1: ... mt
558 lua_pushlightuserdata(L1, const_cast<void*>(lua_topointer(L1, L1_i))); // L1: ... mt decayed
559 lua_replace(L1, L1_i); // L1: ... mt
560 lua_pop(L1, 1); // L1: ...
561 _val_type = LuaType::LIGHTUSERDATA;
562 } else {
563 raise_luaL_error(getErrL(), "Invalid %s mode '%s'", kConvertField.data(), _mode.data()); 574 raise_luaL_error(getErrL(), "Invalid %s mode '%s'", kConvertField.data(), _mode.data());
564 } 575 }
576 _convertMode = ConvertMode::Decay;
565 break; 577 break;
566 578
567 case LuaType::FUNCTION: 579 case LuaType::FUNCTION: // L1: ... <some_function>
568 lua_pushvalue(L1, L1_i); // L1: ... mt kConvertField val 580 _convertMode = ConvertMode::UserConversion;
569 luaW_pushstring(L1, mode == LookupMode::ToKeeper ? "keeper" : "regular"); // L1: ... mt kConvertField val string
570 lua_call(L1, 2, 1); // val:kConvertField(str) -> result // L1: ... mt kConvertField converted
571 lua_replace(L1, L1_i); // L1: ... mt
572 lua_pop(L1, 1); // L1: ... mt
573 _val_type = luaW_type(L1, L1_i);
574 break; 581 break;
575 582
576 default: 583 default:
577 raise_luaL_error(getErrL(), "Invalid %s type %s", kConvertField.data(), luaW_typename(L1, _converterType).data()); 584 raise_luaL_error(getErrL(), "Invalid %s type %s", kConvertField.data(), luaW_typename(L1, _convertType).data());
585 }
586 STACK_CHECK(L1, 1); // L1: ... <converter>
587 return _convertMode;
588}
589
590// #################################################################################################
591
592bool InterCopyContext::processConversion() const
593{
594#if HAVE_LUA_ASSERT() || USE_DEBUG_SPEW()
595 LuaType const _val_type{ luaW_type(L1, L1_i) };
596#endif // HAVE_LUA_ASSERT() || USE_DEBUG_SPEW()
597 LUA_ASSERT(getErrL(), _val_type == LuaType::TABLE || _val_type == LuaType::USERDATA);
598
599 STACK_CHECK_START_REL(L1, 0);
600
601 ConvertMode const _convertMode{ lookupConverter() }; // L1: ... <converter>
602 STACK_CHECK(L1, 1);
603
604 bool _converted{ true };
605 switch (_convertMode) {
606 case ConvertMode::DoNothing:
607 LUA_ASSERT(getErrL(), luaW_type(L1, kIdxTop) == LuaType::NIL); // L1: ... nil
608 lua_pop(L1, 1); // L1: ...
609 _converted = false;
610 break;
611
612 case ConvertMode::ConvertToNil:
613 LUA_ASSERT(getErrL(), kNilSentinel.equals(L1, kIdxTop)); // L1: ... kNilSentinel
614 DEBUGSPEW_CODE(DebugSpew(U) << "converted " << luaW_typename(L1, _val_type) << " to nil" << std::endl);
615 lua_replace(L1, L1_i); // L1: ...
616 break;
617
618 case ConvertMode::Decay:
619 // kConvertField == "decay" -> replace source value with its pointer
620 LUA_ASSERT(getErrL(), luaW_tostring(L1, kIdxTop) == "decay");
621 DEBUGSPEW_CODE(DebugSpew(U) << "converted " << luaW_typename(L1, _val_type) << " to a pointer" << std::endl);
622 lua_pop(L1, 1); // L1: ...
623 lua_pushlightuserdata(L1, const_cast<void*>(lua_topointer(L1, L1_i))); // L1: ... decayed
624 lua_replace(L1, L1_i); // L1: ...
625 break;
626
627 case ConvertMode::UserConversion:
628 lua_pushvalue(L1, L1_i); // L1: ... converter() val
629 luaW_pushstring(L1, mode == LookupMode::ToKeeper ? "keeper" : "regular"); // L1: ... converter() val string
630 lua_call(L1, 2, 1); // val:converter(str) -> result // L1: ... converted
631 lua_replace(L1, L1_i); // L1: ...
632 DEBUGSPEW_CODE(DebugSpew(U) << "converted " << luaW_typename(L1, _val_type) << " to a " << luaW_typename(L1, L1_i) << std::endl);
633 break;
634
635 default:
636 raise_luaL_error(getErrL(), "INTERNAL ERROR: SHOULD NEVER GET HERE");
578 } 637 }
579 STACK_CHECK(L1, 0); 638 STACK_CHECK(L1, 0);
580 LUA_ASSERT(getErrL(), luaW_type(L1, L1_i) == _val_type); 639 return _converted;
581 return _val_type;
582} 640}
583 641
584// ################################################################################################# 642// #################################################################################################
@@ -867,13 +925,11 @@ bool InterCopyContext::tryCopyDeep() const
867 925
868// ################################################################################################# 926// #################################################################################################
869 927
870[[nodiscard]] 928void InterCopyContext::interCopyBoolean() const
871bool InterCopyContext::interCopyBoolean() const
872{ 929{
873 int const _v{ lua_toboolean(L1, L1_i) }; 930 int const _v{ lua_toboolean(L1, L1_i) };
874 DEBUGSPEW_CODE(DebugSpew(nullptr) << (_v ? "true" : "false") << std::endl); 931 DEBUGSPEW_CODE(DebugSpew(nullptr) << (_v ? "true" : "false") << std::endl);
875 lua_pushboolean(L2, _v); 932 lua_pushboolean(L2, _v);
876 return true;
877} 933}
878 934
879// ################################################################################################# 935// #################################################################################################
@@ -972,8 +1028,7 @@ bool InterCopyContext::interCopyFunction() const
972 1028
973// ################################################################################################# 1029// #################################################################################################
974 1030
975[[nodiscard]] 1031void InterCopyContext::interCopyLightuserdata() const
976bool InterCopyContext::interCopyLightuserdata() const
977{ 1032{
978 void* const _p{ lua_touserdata(L1, L1_i) }; 1033 void* const _p{ lua_touserdata(L1, L1_i) };
979 // recognize and print known UniqueKey names here 1034 // recognize and print known UniqueKey names here
@@ -999,7 +1054,6 @@ bool InterCopyContext::interCopyLightuserdata() const
999 lua_pushlightuserdata(L2, _p); 1054 lua_pushlightuserdata(L2, _p);
1000 DEBUGSPEW_CODE(DebugSpew(nullptr) << std::endl); 1055 DEBUGSPEW_CODE(DebugSpew(nullptr) << std::endl);
1001 } 1056 }
1002 return true;
1003} 1057}
1004 1058
1005// ################################################################################################# 1059// #################################################################################################
@@ -1021,8 +1075,7 @@ bool InterCopyContext::interCopyNil() const
1021 1075
1022// ################################################################################################# 1076// #################################################################################################
1023 1077
1024[[nodiscard]] 1078void InterCopyContext::interCopyNumber() const
1025bool InterCopyContext::interCopyNumber() const
1026{ 1079{
1027 // LNUM patch support (keeping integer accuracy) 1080 // LNUM patch support (keeping integer accuracy)
1028#if defined LUA_LNUM || LUA_VERSION_NUM >= 503 1081#if defined LUA_LNUM || LUA_VERSION_NUM >= 503
@@ -1037,40 +1090,44 @@ bool InterCopyContext::interCopyNumber() const
1037 DEBUGSPEW_CODE(DebugSpew(nullptr) << _v << std::endl); 1090 DEBUGSPEW_CODE(DebugSpew(nullptr) << _v << std::endl);
1038 lua_pushnumber(L2, _v); 1091 lua_pushnumber(L2, _v);
1039 } 1092 }
1040 return true;
1041} 1093}
1042 1094
1043// ################################################################################################# 1095// #################################################################################################
1044 1096
1045[[nodiscard]] 1097void InterCopyContext::interCopyString() const
1046bool InterCopyContext::interCopyString() const
1047{ 1098{
1048 std::string_view const _s{ luaW_tostring(L1, L1_i) }; 1099 std::string_view const _s{ luaW_tostring(L1, L1_i) };
1049 DEBUGSPEW_CODE(DebugSpew(nullptr) << "'" << _s << "'" << std::endl); 1100 DEBUGSPEW_CODE(DebugSpew(nullptr) << "'" << _s << "'" << std::endl);
1050 luaW_pushstring(L2, _s); 1101 luaW_pushstring(L2, _s);
1051 return true;
1052} 1102}
1053 1103
1054// ################################################################################################# 1104// #################################################################################################
1055 1105
1056[[nodiscard]] 1106[[nodiscard]]
1057bool InterCopyContext::interCopyTable() const 1107InterCopyOneResult InterCopyContext::interCopyTable() const
1058{ 1108{
1059 if (vt == VT::KEY) { 1109 if (vt == VT::KEY) {
1060 return false; 1110 return InterCopyOneResult::NotCopied;
1061 } 1111 }
1062 1112
1063 STACK_CHECK_START_REL(L1, 0); 1113 STACK_CHECK_START_REL(L1, 0);
1064 STACK_CHECK_START_REL(L2, 0); 1114 STACK_CHECK_START_REL(L2, 0);
1065 DEBUGSPEW_CODE(DebugSpew(nullptr) << "TABLE " << name << std::endl); 1115 DEBUGSPEW_CODE(DebugSpew(nullptr) << "TABLE " << name << std::endl);
1066 1116
1117 // replace the value at L1_i with the result of a conversion if required
1118 bool const _converted{ processConversion() };
1119 if (_converted) {
1120 return InterCopyOneResult::RetryAfterConversion;
1121 }
1122 STACK_CHECK(L1, 0);
1123
1067 /* 1124 /*
1068 * First, let's try to see if this table is special (aka is it some table that we registered in our lookup databases during module registration?) 1125 * First, let's try to see if this table is special (aka is it some table that we registered in our lookup databases during module registration?)
1069 * Note that this table CAN be a module table, but we just didn't register it, in which case we'll send it through the table cloning mechanism 1126 * Note that this table CAN be a module table, but we just didn't register it, in which case we'll send it through the table cloning mechanism
1070 */ 1127 */
1071 if (lookupTable()) { 1128 if (lookupTable()) {
1072 LUA_ASSERT(L1, lua_istable(L2, -1) || (lua_tocfunction(L2, -1) == table_lookup_sentinel)); // from lookup data. can also be table_lookup_sentinel if this is a table we know 1129 LUA_ASSERT(L1, lua_istable(L2, -1) || (lua_tocfunction(L2, -1) == table_lookup_sentinel)); // from lookup data. can also be table_lookup_sentinel if this is a table we know
1073 return true; 1130 return InterCopyOneResult::Copied;
1074 } 1131 }
1075 1132
1076 /* Check if we've already copied the same table from 'L1' (during this transmission), and 1133 /* Check if we've already copied the same table from 'L1' (during this transmission), and
@@ -1084,7 +1141,7 @@ bool InterCopyContext::interCopyTable() const
1084 */ 1141 */
1085 if (pushCachedTable()) { // L2: ... t 1142 if (pushCachedTable()) { // L2: ... t
1086 LUA_ASSERT(L1, lua_istable(L2, -1)); // from cache 1143 LUA_ASSERT(L1, lua_istable(L2, -1)); // from cache
1087 return true; 1144 return InterCopyOneResult::Copied;
1088 } 1145 }
1089 LUA_ASSERT(L1, lua_istable(L2, -1)); 1146 LUA_ASSERT(L1, lua_istable(L2, -1));
1090 1147
@@ -1106,25 +1163,25 @@ bool InterCopyContext::interCopyTable() const
1106 } 1163 }
1107 STACK_CHECK(L2, 1); 1164 STACK_CHECK(L2, 1);
1108 STACK_CHECK(L1, 0); 1165 STACK_CHECK(L1, 0);
1109 return true; 1166 return InterCopyOneResult::Copied;
1110} 1167}
1111 1168
1112// ################################################################################################# 1169// #################################################################################################
1113 1170
1114[[nodiscard]] 1171[[nodiscard]]
1115bool InterCopyContext::interCopyUserdata() const 1172InterCopyOneResult InterCopyContext::interCopyUserdata() const
1116{ 1173{
1117 STACK_CHECK_START_REL(L1, 0); 1174 STACK_CHECK_START_REL(L1, 0);
1118 STACK_CHECK_START_REL(L2, 0); 1175 STACK_CHECK_START_REL(L2, 0);
1119 if (vt == VT::KEY) { 1176 if (vt == VT::KEY) {
1120 return false; 1177 return InterCopyOneResult::NotCopied;
1121 } 1178 }
1122 1179
1123 // try clonable userdata first 1180 // try clonable userdata first
1124 if (tryCopyClonable()) { 1181 if (tryCopyClonable()) {
1125 STACK_CHECK(L1, 0); 1182 STACK_CHECK(L1, 0);
1126 STACK_CHECK(L2, 1); 1183 STACK_CHECK(L2, 1);
1127 return true; 1184 return InterCopyOneResult::Copied;
1128 } 1185 }
1129 1186
1130 STACK_CHECK(L1, 0); 1187 STACK_CHECK(L1, 0);
@@ -1134,13 +1191,20 @@ bool InterCopyContext::interCopyUserdata() const
1134 if (tryCopyDeep()) { 1191 if (tryCopyDeep()) {
1135 STACK_CHECK(L1, 0); 1192 STACK_CHECK(L1, 0);
1136 STACK_CHECK(L2, 1); 1193 STACK_CHECK(L2, 1);
1137 return true; 1194 return InterCopyOneResult::Copied;
1195 }
1196
1197 // replace the value at L1_i with the result of a conversion if required
1198 bool const _converted{ processConversion() };
1199 if (_converted) {
1200 return InterCopyOneResult::RetryAfterConversion;
1138 } 1201 }
1202 STACK_CHECK(L1, 0);
1139 1203
1140 // Last, let's try to see if this userdata is special (aka is it some userdata that we registered in our lookup databases during module registration?) 1204 // Last, let's try to see if this userdata is special (aka is it some userdata that we registered in our lookup databases during module registration?)
1141 if (lookupUserdata()) { 1205 if (lookupUserdata()) {
1142 LUA_ASSERT(L1, luaW_type(L2, kIdxTop) == LuaType::USERDATA || (lua_tocfunction(L2, kIdxTop) == userdata_lookup_sentinel)); // from lookup data. can also be userdata_lookup_sentinel if this is a userdata we know 1206 LUA_ASSERT(L1, luaW_type(L2, kIdxTop) == LuaType::USERDATA || (lua_tocfunction(L2, kIdxTop) == userdata_lookup_sentinel)); // from lookup data. can also be userdata_lookup_sentinel if this is a userdata we know
1143 return true; 1207 return InterCopyOneResult::Copied;
1144 } 1208 }
1145 1209
1146 raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes"); 1210 raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes");
@@ -1193,54 +1257,64 @@ InterCopyResult InterCopyContext::interCopyOne() const
1193 DEBUGSPEW_CODE(DebugSpew(U) << "interCopyOne()" << std::endl); 1257 DEBUGSPEW_CODE(DebugSpew(U) << "interCopyOne()" << std::endl);
1194 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ U }); 1258 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ U });
1195 1259
1196 // replace the value at L1_i with the result of a conversion if required 1260 DEBUGSPEW_CODE(DebugSpew(U) << local::sLuaTypeNames[static_cast<int>(luaW_type(L1, L1_i))] << " " << local::sValueTypeNames[static_cast<int>(vt)] << ": ");
1197 LuaType const _val_type{ processConversion() };
1198 STACK_CHECK(L1, 0);
1199 DEBUGSPEW_CODE(DebugSpew(U) << local::sLuaTypeNames[static_cast<int>(_val_type)] << " " << local::sValueTypeNames[static_cast<int>(vt)] << ": ");
1200
1201 // Lets push nil to L2 if the object should be ignored
1202 bool _ret{ true };
1203 switch (_val_type) {
1204 // Basic types allowed both as values, and as table keys
1205 case LuaType::BOOLEAN:
1206 _ret = interCopyBoolean();
1207 break;
1208 case LuaType::NUMBER:
1209 _ret = interCopyNumber();
1210 break;
1211 case LuaType::STRING:
1212 _ret = interCopyString();
1213 break;
1214 case LuaType::LIGHTUSERDATA:
1215 _ret = interCopyLightuserdata();
1216 break;
1217 1261
1218 // The following types are not allowed as table keys 1262 auto _tryCopy = [this]() {
1219 case LuaType::USERDATA: 1263 LuaType const _val_type{ luaW_type(L1, L1_i) };
1220 _ret = interCopyUserdata(); 1264 InterCopyOneResult _result{ InterCopyOneResult::Copied };
1221 break; 1265 switch (_val_type) {
1222 case LuaType::NIL: 1266 // Basic types allowed both as values, and as table keys
1223 _ret = interCopyNil(); 1267 case LuaType::BOOLEAN:
1224 break; 1268 interCopyBoolean();
1225 case LuaType::FUNCTION: 1269 break;
1226 _ret = interCopyFunction(); 1270 case LuaType::NUMBER:
1227 break; 1271 interCopyNumber();
1228 case LuaType::TABLE: 1272 break;
1229 _ret = interCopyTable(); 1273 case LuaType::STRING:
1230 break; 1274 interCopyString();
1275 break;
1276 case LuaType::LIGHTUSERDATA:
1277 interCopyLightuserdata();
1278 break;
1231 1279
1232 // The following types cannot be copied 1280 // The following types are not allowed as table keys
1233 case LuaType::NONE: 1281 case LuaType::USERDATA:
1234 case LuaType::CDATA: 1282 _result = interCopyUserdata();
1235 [[fallthrough]]; 1283 break;
1236 case LuaType::THREAD: 1284 case LuaType::NIL:
1237 _ret = false; 1285 _result = interCopyNil() ? InterCopyOneResult::Copied : InterCopyOneResult::NotCopied;
1238 break; 1286 break;
1239 } 1287 case LuaType::FUNCTION:
1288 _result = interCopyFunction() ? InterCopyOneResult::Copied : InterCopyOneResult::NotCopied;
1289 break;
1290 case LuaType::TABLE:
1291 _result = interCopyTable();
1292 break;
1293
1294 // The following types cannot be copied
1295 case LuaType::NONE:
1296 case LuaType::CDATA:
1297 [[fallthrough]];
1298
1299 case LuaType::THREAD:
1300 _result = InterCopyOneResult::NotCopied;
1301 break;
1302 }
1303 return _result;
1304 };
1305
1306 uint32_t _conversionCount{ 0 };
1307 InterCopyOneResult _result{ InterCopyOneResult::Copied };
1308 do {
1309 if (_conversionCount++ > U->convertMaxAttempts) {
1310 raise_luaL_error(getErrL(), "more than %d conversion attempts", U->convertMaxAttempts);
1311 }
1312 _result = _tryCopy();
1313 } while (_result == InterCopyOneResult::RetryAfterConversion);
1240 1314
1241 STACK_CHECK(L2, _ret ? 1 : 0); 1315 STACK_CHECK(L2, (_result == InterCopyOneResult::Copied) ? 1 : 0);
1242 STACK_CHECK(L1, 0); 1316 STACK_CHECK(L1, 0);
1243 return _ret ? InterCopyResult::Success : InterCopyResult::Error; 1317 return (_result == InterCopyOneResult::Copied) ? InterCopyResult::Success : InterCopyResult::Error;
1244} 1318}
1245 1319
1246// ################################################################################################# 1320// #################################################################################################