diff options
author | Andrei Karas <akaras@inbox.ru> | 2012-01-04 20:53:28 +0300 |
---|---|---|
committer | Andrei Karas <akaras@inbox.ru> | 2012-01-04 20:58:00 +0300 |
commit | 11b1348fd4b52a35851a03a9c50bd962e385a6a9 (patch) | |
tree | cff1c3237c2cf05cb9f4de9b5084762ce374a947 /src | |
parent | a7aa62d82574128cba8fd3c10d29e2bbcfca305d (diff) | |
download | manaplus-11b1348fd4b52a35851a03a9c50bd962e385a6a9.tar.gz manaplus-11b1348fd4b52a35851a03a9c50bd962e385a6a9.tar.bz2 manaplus-11b1348fd4b52a35851a03a9c50bd962e385a6a9.tar.xz manaplus-11b1348fd4b52a35851a03a9c50bd962e385a6a9.zip |
Add ability for testing drivers and graphics.
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/client.cpp | 105 | ||||
-rw-r--r-- | src/client.h | 25 | ||||
-rw-r--r-- | src/main.cpp | 24 | ||||
-rw-r--r-- | src/test/testlauncher.cpp | 170 | ||||
-rw-r--r-- | src/test/testlauncher.h | 54 | ||||
-rw-r--r-- | src/test/testmain.cpp | 287 | ||||
-rw-r--r-- | src/test/testmain.h | 64 |
9 files changed, 722 insertions, 15 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b7005b144..18379fdf0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -459,6 +459,10 @@ SET(SRCS utils/mkdir.h utils/xml.cpp utils/xml.h + test/testlauncher.cpp + test/testlauncher.h + test/testmain.cpp + test/testmain.h actor.cpp actor.h actorsprite.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 527893598..ead1224d6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -466,6 +466,10 @@ manaplus_SOURCES += gui/widgets/avatarlistbox.cpp \ utils/mutex.h \ utils/xml.cpp \ utils/xml.h \ + test/testlauncher.cpp \ + test/testlauncher.h \ + test/testmain.cpp \ + test/testmain.h \ actor.cpp \ actor.h \ actorsprite.cpp \ diff --git a/src/client.cpp b/src/client.cpp index b5cbffd40..4b0319954 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -101,6 +101,9 @@ #include "utils/paths.h" #include "utils/stringutils.h" +#include "test/testlauncher.h" +#include "test/testmain.h" + #ifdef __APPLE__ #include <CoreFoundation/CFBundle.h> #endif @@ -269,7 +272,25 @@ Client::Client(const Options &options): mGuiAlpha(1.0f) { mInstance = this; +} + +void Client::testsInit() +{ + printf ("testInit\n"); + if (!mOptions.test.empty()) + { + gameInit(); + } + else + { + logger = new Logger; + initLocalDataDir(); + initConfigDir(); + } +} +void Client::gameInit() +{ logger = new Logger; // Load branding information @@ -644,7 +665,31 @@ Client::Client(const Options &options): Client::~Client() { - logger->log1("Quitting1"); + if (!mOptions.testMode) + gameClear(); + else + testsClear(); +} + +void Client::testsClear() +{ + if (!mOptions.test.empty()) + { + gameClear(); + } + else + { + BeingInfo::clear(); + + delete logger; + logger = nullptr; + } +} + +void Client::gameClear() +{ + if (logger) + logger->log1("Quitting1"); config.removeListener("fpslimit", this); config.removeListener("guialpha", this); @@ -681,39 +726,46 @@ Client::~Client() player_relations.store(); - logger->log1("Quitting2"); + if (logger) + logger->log1("Quitting2"); delete gui; gui = nullptr; - logger->log1("Quitting3"); + if (logger) + logger->log1("Quitting3"); delete mainGraphics; mainGraphics = nullptr; - logger->log1("Quitting4"); + if (logger) + logger->log1("Quitting4"); // Shutdown libxml xmlCleanupParser(); - logger->log1("Quitting5"); + if (logger) + logger->log1("Quitting5"); BeingInfo::clear(); // Shutdown sound sound.close(); - logger->log1("Quitting6"); + if (logger) + logger->log1("Quitting6"); ActorSprite::unload(); ResourceManager::deleteInstance(); - logger->log1("Quitting8"); + if (logger) + logger->log1("Quitting8"); SDL_FreeSurface(mIcon); - logger->log1("Quitting9"); + if (logger) + logger->log1("Quitting9"); delete userPalette; userPalette = nullptr; @@ -721,7 +773,8 @@ Client::~Client() delete joystick; joystick = nullptr; - logger->log1("Quitting10"); + if (logger) + logger->log1("Quitting10"); config.write(); serverConfig.write(); @@ -729,7 +782,8 @@ Client::~Client() config.clear(); serverConfig.clear(); - logger->log1("Quitting11"); + if (logger) + logger->log1("Quitting11"); delete chatLogger; chatLogger = nullptr; @@ -740,7 +794,22 @@ Client::~Client() mInstance = nullptr; } -int Client::exec() +int Client::testsExec() +{ + if (mOptions.test.empty()) + { + TestMain test; + return test.exec(); + } + else + { + TestLauncher launcher(mOptions.test); + return launcher.exec(); + } + return 0; +} + +int Client::gameExec() { int lastTickTime = tick_time; @@ -1481,6 +1550,12 @@ void Client::initRootDir() */ void Client::initHomeDir() { + initLocalDataDir(); + initConfigDir(); +} + +void Client::initLocalDataDir() +{ mLocalDataDir = mOptions.localDataDir; if (mLocalDataDir.empty()) @@ -1509,7 +1584,10 @@ void Client::initHomeDir() logger->error(strprintf(_("%s doesn't exist and can't be created! " "Exiting."), mLocalDataDir.c_str())); } +} +void Client::initConfigDir() +{ mConfigDir = mOptions.configDir; if (mConfigDir.empty()) @@ -1623,7 +1701,10 @@ void Client::initConfiguration() // bool oldConfig = false; // int emptySize = config.getSize(); - configPath = mConfigDir + "/config.xml"; + if (mOptions.test.empty()) + configPath = mConfigDir + "/config.xml"; + else + configPath = mConfigDir + "/test.xml"; configFile = fopen(configPath.c_str(), "r"); diff --git a/src/client.h b/src/client.h index fcb14a660..22eb406eb 100644 --- a/src/client.h +++ b/src/client.h @@ -163,6 +163,7 @@ public: chooseDefault(false), noOpenGL(false), safeMode(false), + testMode(false), serverPort(0) {} @@ -184,6 +185,8 @@ public: std::string localDataDir; std::string screenshotDir; bool safeMode; + bool testMode; + std::string test; std::string serverName; short serverPort; @@ -198,7 +201,13 @@ public: static Client *instance() { return mInstance; } - int exec(); + void gameInit(); + + void testsInit(); + + int gameExec(); + + int testsExec(); static void setState(State state) { instance()->mState = state; } @@ -280,19 +289,33 @@ public: private: void initRootDir(); + void initHomeDir(); + void initConfiguration(); + + void initLocalDataDir(); + + void initConfigDir(); + void initUpdatesDir(); + void initScreenshotDir(); + void initServerConfig(std::string serverName); bool copyFile(std::string &configPath, std::string &oldConfigPath); + bool createConfig(std::string &configPath); void accountLogin(LoginData *data); void storeSafeParameters(); + void gameClear(); + + void testsClear(); + static Client *mInstance; Options mOptions; diff --git a/src/main.cpp b/src/main.cpp index 281687809..31d88082f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,6 +71,7 @@ static void printHelp() " directory") << endl << _(" --screenshot-dir : Directory to store screenshots") << endl << _(" --safemode : Start game in safe mode") << endl + << _(" -T --tests : Start testing drivers and auto configuring") << endl #ifdef USE_OPENGL << _(" --no-opengl : Disable OpenGL for this session") << endl #endif @@ -84,7 +85,7 @@ static void printVersion() static void parseOptions(int argc, char *argv[], Client::Options &options) { - const char *optstring = "hvud:U:P:Dc:p:l:L:C:s:"; + const char *optstring = "hvud:U:P:Dc:p:l:L:C:s:t:T"; const struct option long_options[] = { @@ -107,6 +108,8 @@ static void parseOptions(int argc, char *argv[], Client::Options &options) { "chat-log-dir", required_argument, 0, 'L' }, { "screenshot-dir", required_argument, 0, 'i' }, { "safemode", no_argument, 0, 'm' }, + { "tests", no_argument, 0, 'T' }, + { "test", required_argument, 0, 't' }, { nullptr, 0, 0, 0 } }; @@ -174,6 +177,14 @@ static void parseOptions(int argc, char *argv[], Client::Options &options) case 'm': options.safeMode = true; break; + case 'T': + options.testMode = true; + options.test = ""; + break; + case 't': + options.testMode = true; + options.test = std::string(optarg); + break; default: break; } @@ -247,5 +258,14 @@ int main(int argc, char *argv[]) SetCurrentDirectory(PHYSFS_getBaseDir()); #endif Client client(options); - return client.exec(); + if (!options.testMode) + { + client.gameInit(); + return client.gameExec(); + } + else + { + client.testsInit(); + return client.testsExec(); + } } diff --git a/src/test/testlauncher.cpp b/src/test/testlauncher.cpp new file mode 100644 index 000000000..5c532a42c --- /dev/null +++ b/src/test/testlauncher.cpp @@ -0,0 +1,170 @@ +/* + * The ManaPlus Client + * Copyright (C) 2011 The ManaPlus Developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "test/testlauncher.h" + +#include "client.h" +#include "configuration.h" +#include "graphics.h" +#include "localconsts.h" +#include "logger.h" +#include "sound.h" + +#include "gui/theme.h" + +#include "utils/gettext.h" +#include "utils/mkdir.h" +#include "utils/stringutils.h" + +#include "resources/image.h" +#include "resources/wallpaper.h" + +#ifdef WIN32 +#include <windows.h> +#define sleep(seconds) Sleep((seconds) * 1000) +#endif + +//#include <unistd.h> + +#include "debug.h" + +TestLauncher::TestLauncher(std::string test) : + mTest(test) +{ + file.open((Client::getLocalDataDirectory() + + std::string("/test.log")).c_str(), std::ios::out); +} + +TestLauncher::~TestLauncher() +{ + file.close(); +} + +int TestLauncher::exec() +{ + if (mTest == "1" || mTest == "2" || mTest == "3") + return testBackend(); + else if (mTest == "4") + return testSound(); + else if (mTest == "5" || mTest == "6" || mTest == "7") + return testRescale(); + else if (mTest == "8" || mTest == "9" || mTest == "10") + return testFps(); + + return -1; +} + +int TestLauncher::testBackend() +{ + Image *img = Theme::getImageFromTheme("graphics/sprites/arrow_up.gif"); + if (!img) + return 1; + int cnt = 100; + + for (int f = 0; f < cnt; f ++) + { + mainGraphics->drawImage(img, cnt * 7, cnt * 5); + mainGraphics->updateScreen(); + } + + sleep(1); + return 0; +} + +int TestLauncher::testSound() +{ + sound.playGuiSfx("system/newmessage.ogg"); + sleep(1); + sound.playSfx("system/newmessage.ogg", 0, 0); + sound.playMusic("sfx/system/newmessage.ogg"); + sleep(3); + sound.stopMusic(); + return 0; +} + +int TestLauncher::testRescale() +{ + Wallpaper::loadWallpapers(); + const std::string wallpaperName = Wallpaper::getWallpaper(800, 600); + Image *img = Theme::getImageFromTheme(wallpaperName); + if (!img) + return 1; + + sleep(1); + return 0; +} + +int TestLauncher::testFps() +{ + timeval start; + timeval end; + + Wallpaper::loadWallpapers(); + const std::string wallpaperName = Wallpaper::getWallpaper(800, 600); + Image *img[4]; + + img[0] = Theme::getImageFromTheme("graphics/sprites/arrow_up.gif"); + img[1] = Theme::getImageFromTheme("graphics/sprites/arrow_down.gif"); + img[2] = Theme::getImageFromTheme("graphics/sprites/arrow_left.gif"); + img[3] = Theme::getImageFromTheme("graphics/sprites/arrow_right.gif"); + int idx = 0; + + int cnt = 500; + + gettimeofday(&start, NULL); + for (int k = 0; k < cnt; k ++) + { + for (int x = 0; x < 800; x += 20) + { + for (int y = 0; y < 600; y += 25) + { + mainGraphics->drawImage(img[idx], x, y); + idx ++; + if (idx > 3) + idx = 0; + } + } + mainGraphics->updateScreen(); + } + + gettimeofday(&end, NULL); + int tFps = calcFps(&start, &end, cnt); + file << mTest << std::endl; + file << tFps << std::endl; + + sleep(1); + return 0; +} + +int TestLauncher::calcFps(timeval *start, timeval *end, int calls) +{ + long mtime; + long seconds; + long useconds; + + seconds = end->tv_sec - start->tv_sec; + useconds = end->tv_usec - start->tv_usec; + + mtime = (seconds * 1000 + useconds / 1000.0) + 0.5; + if (mtime == 0) + return 100000; + + return static_cast<long>(calls) * 1000 / mtime; +} diff --git a/src/test/testlauncher.h b/src/test/testlauncher.h new file mode 100644 index 000000000..19a057722 --- /dev/null +++ b/src/test/testlauncher.h @@ -0,0 +1,54 @@ +/* + * The ManaPlus Client + * Copyright (C) 2011 The ManaPlus Developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TEST_TESTLAUNCHER_H +#define TEST_TESTLAUNCHER_H + +#include "logger.h" + +#include <string> +#include <sys/time.h> + +class TestLauncher +{ + public: + TestLauncher(std::string test); + + ~TestLauncher(); + + int exec(); + + int calcFps(timeval *start, timeval *end, int calls); + + int testBackend(); + + int testSound(); + + int testRescale(); + + int testFps(); + + private: + std::string mTest; + + std::ofstream file; +}; + +#endif // TEST_TESTLAUNCHER_H diff --git a/src/test/testmain.cpp b/src/test/testmain.cpp new file mode 100644 index 000000000..db82592b3 --- /dev/null +++ b/src/test/testmain.cpp @@ -0,0 +1,287 @@ +/* + * The ManaPlus Client + * Copyright (C) 2011 The ManaPlus Developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "test/testmain.h" + +#include "utils/gettext.h" + +#include "client.h" +#include "configuration.h" +#include "localconsts.h" + +#include "utils/gettext.h" +#include "utils/mkdir.h" +#include "utils/stringutils.h" +#include "utils/process.h" + +#include <iostream> + +#include "debug.h" + +#ifdef WIN32 + const std::string fileName = "manaplus.exe"; +#else + const std::string fileName = "manaplus"; +#endif + +TestMain::TestMain() +{ + log = new Logger; +// log->setLogFile(Client::getLocalDataDirectory() +// + std::string("/test.log")); + log->setLogFile(Client::getLocalDataDirectory() + + std::string("/manaplustest.log")); +} + +void TestMain::initConfig() +{ + config.init(Client::getConfigDirectory() + "/test.xml"); +// config.setDefaultValues(getConfigDefaults()); + + config.setValue("hwaccel", false); + config.setValue("screen", false); + config.setValue("sound", false); + config.setValue("guialpha", 0.8f); + config.setValue("remember", true); + config.setValue("sfxVolume", 50); + config.setValue("musicVolume", 60); + config.setValue("fpslimit", 0); + config.setValue("customcursor", true); + config.setValue("useScreenshotDirectorySuffix", true); + config.setValue("ChatLogLength", 128); + config.setValue("screenwidth", 800); + config.setValue("screenheight", 600); +} + +int TestMain::exec() +{ + initConfig(); + int softwareTest = invokeSoftwareRenderTest("1"); + int fastOpenGLTest = invokeFastOpenGLRenderTest("2"); + int safeOpenGLTest = invokeSafeOpenGLRenderTest("3"); + int soundTest = invokeTest4(); + int rescaleTest[3]; + int softFpsTest = -1; + int fastOpenGLFpsTest = -1; + int safeOpenGLFpsTest = -1; + int softFps = 0; + int fastOpenGLFps = 0; + int safeOpenGLFps = 0; + + int openGLMode = 0; + int maxFps = 0; + rescaleTest[0] = -1; + rescaleTest[1] = -1; + rescaleTest[2] = -1; + std::string info; + + info += strprintf("%d.%d,%d,%d.", soundTest, softwareTest, + fastOpenGLTest, safeOpenGLTest); + + if (!softwareTest) + { + softFpsTest = invokeSoftwareRenderTest("8"); + info += strprintf ("%d", softFpsTest); + if (!softFpsTest) + { + softFps = readValue(8); + info += strprintf (",%d", softFps); + if (!softFps) + { + softwareTest = -1; + softFpsTest = -1; + } + else + { + rescaleTest[0] = invokeSoftwareRenderTest("5"); + info += strprintf (",%d", rescaleTest[0]); + } + } + else + { + softwareTest = -1; + } + } + info += "."; + if (!fastOpenGLTest) + { + fastOpenGLFpsTest = invokeFastOpenGLRenderTest("9"); + info += strprintf ("%d", fastOpenGLFpsTest); + if (!fastOpenGLFpsTest) + { + fastOpenGLFps = readValue(9); + info += strprintf (",%d", fastOpenGLFps); + if (!fastOpenGLFps) + { + fastOpenGLTest = -1; + fastOpenGLFpsTest = -1; + } + else + { + rescaleTest[1] = invokeFastOpenGLRenderTest("6"); + info += strprintf (",%d", rescaleTest[1]); + } + } + else + { + fastOpenGLTest = -1; + } + } + info += "."; + if (!safeOpenGLTest) + { + safeOpenGLFpsTest = invokeSafeOpenGLRenderTest("10"); + info += strprintf ("%d", safeOpenGLFpsTest); + if (!safeOpenGLFpsTest) + { + safeOpenGLFps = readValue(10); + info += strprintf (",%d", safeOpenGLFps); + if (!safeOpenGLFps) + { + safeOpenGLTest = -1; + safeOpenGLFpsTest = -1; + } + else + { + rescaleTest[2] = invokeSafeOpenGLRenderTest("7"); + info += strprintf (",%d", rescaleTest[2]); + } + } + else + { + safeOpenGLTest = -1; + } + } + info += "."; + + maxFps = softFps; + if (maxFps < fastOpenGLFps) + { + openGLMode = 1; + maxFps = fastOpenGLFps; + } + if (maxFps < safeOpenGLFps) + { + openGLMode = 2; + maxFps = safeOpenGLFps; + } + + writeConfig(openGLMode, rescaleTest[openGLMode], soundTest, info); + return 0; +} + +void TestMain::writeConfig(int openGLMode, int rescale, + int sound, std::string info) +{ + config.init(Client::getConfigDirectory() + "/config.xml"); + + // searched values + config.setValue("opengl", openGLMode); + config.setValue("showBackground", !rescale); + config.setValue("sound", !sound); + + // better perfomance + config.setValue("hwaccel", true); + config.setValue("fpslimit", 60); + config.setValue("altfpslimit", 2); + config.setValue("safemode", false); + config.setValue("enableMapReduce", true); + + // stats + config.setValue("testInfo", info); + + config.write(); +} + +int TestMain::readValue(int ver, int def) +{ + std::string tmp; + int var; + file.open((Client::getLocalDataDirectory() + + std::string("/test.log")).c_str(), std::ios::in); + if (!getline(file, tmp)) + { + file.close(); + return def; + } + var = atoi(tmp.c_str()); + if (ver != var || !getline(file, tmp)) + { + file.close(); + return def; + } + def = atoi(tmp.c_str()); + file.close(); + log->log("value for %d = %d", ver, def); + return def; +} + +int TestMain::invokeTest(std::string test) +{ + config.setValue("opengl", 0); + + config.write(); + int ret = execFile(fileName, fileName, "-t", test); + return ret; +} + +int TestMain::invokeTest4() +{ + config.setValue("sound", true); + int ret = invokeTest("4"); + + log->log("4: %d", ret); + return ret; +} + +int TestMain::invokeSoftwareRenderTest(std::string test) +{ + config.setValue("opengl", 0); + config.write(); + int ret = execFile(fileName, fileName, "-t", test, 30); + log->log("%s: %d", test.c_str(), ret); + return ret; +} + +int TestMain::invokeFastOpenGLRenderTest(std::string test) +{ +#if defined USE_OPENGL + config.setValue("opengl", 1); +#else + return -1; +#endif + config.write(); + int ret = execFile(fileName, fileName, "-t", test, 30); + log->log("%s: %d", test.c_str(), ret); + return ret; +} + +int TestMain::invokeSafeOpenGLRenderTest(std::string test) +{ +#if defined USE_OPENGL + config.setValue("opengl", 2); +#else + return -1; +#endif + config.write(); + int ret = execFile(fileName, fileName, "-t", test, 30); + log->log("%s: %d", test.c_str(), ret); + return ret; +} diff --git a/src/test/testmain.h b/src/test/testmain.h new file mode 100644 index 000000000..de1229454 --- /dev/null +++ b/src/test/testmain.h @@ -0,0 +1,64 @@ +/* + * The ManaPlus Client + * Copyright (C) 2011 The ManaPlus Developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TEST_TESTMAIN_H +#define TEST_TESTMAIN_H + +#include "logger.h" + +#include <string> + +class TestMain +{ + public: + TestMain(); + + int exec(); + + private: + void initConfig(); + + int readValue(int ver, int def = 0); + + int invokeTest(std::string test); + + int invokeTest3(); + + int invokeTest4(); + + int invokeTest7(); + + int invokeSoftwareRenderTest(std::string test); + + int invokeFastOpenGLRenderTest(std::string test); + + int invokeSafeOpenGLRenderTest(std::string test); + + void testsMain(); + + void writeConfig(int openGLMode, int rescale, + int sound, std::string info); + + Logger *log; + + std::ifstream file; +}; + +#endif // TEST_TESTMAIN_H |