Line data Source code
1 :
2 : /* Copyright (c) 2005-2017, Stefan Eilemann <eile@equalizergraphics.com>
3 : * Daniel Nachbaur <danielnachbaur@gmail.com>
4 : *
5 : * This library is free software; you can redistribute it and/or modify it under
6 : * the terms of the GNU Lesser General Public License version 2.1 as published
7 : * by the Free Software Foundation.
8 : *
9 : * This library is distributed in the hope that it will be useful, but WITHOUT
10 : * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 : * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12 : * details.
13 : *
14 : * You should have received a copy of the GNU Lesser General Public License
15 : * along with this library; if not, write to the Free Software Foundation, Inc.,
16 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 : */
18 :
19 : #include "client.h"
20 :
21 : #include "commandQueue.h"
22 : #include "config.h"
23 : #include "global.h"
24 : #include "init.h"
25 : #include "node.h"
26 : #include "nodeFactory.h"
27 : #include "server.h"
28 :
29 : #include <eq/fabric/commands.h>
30 : #include <eq/fabric/configVisitor.h>
31 : #include <eq/fabric/elementVisitor.h>
32 : #include <eq/fabric/leafVisitor.h>
33 : #include <eq/fabric/nodeType.h>
34 : #include <eq/fabric/view.h>
35 :
36 : #include <eq/server/localServer.h>
37 :
38 : #include <co/connection.h>
39 : #include <co/connectionDescription.h>
40 : #include <co/global.h>
41 : #include <co/iCommand.h>
42 : #include <lunchbox/dso.h>
43 : #include <lunchbox/file.h>
44 : #include <lunchbox/term.h>
45 :
46 : #include <boost/filesystem/path.hpp>
47 : #include <boost/program_options/options_description.hpp>
48 : #include <boost/program_options/parsers.hpp>
49 : #include <boost/program_options/variables_map.hpp>
50 :
51 : #ifdef _MSC_VER
52 : #include <direct.h>
53 : #define chdir _chdir
54 : #endif
55 :
56 : #ifdef EQ_QT_USED
57 : #include <QApplication> // must be included before any header defining Bool
58 :
59 : #include "os.h"
60 : #else
61 : class QApplication;
62 : #endif
63 :
64 : namespace eq
65 : {
66 : namespace
67 : {
68 : typedef std::unordered_set<Server*> ServerSet;
69 : }
70 :
71 : /** @cond IGNORE */
72 : namespace detail
73 : {
74 1 : class Client
75 : {
76 : public:
77 1 : Client()
78 1 : : queue(co::Global::getCommandQueueLimit())
79 : , modelUnit(EQ_UNDEFINED_UNIT)
80 : , qtApp(0)
81 1 : , running(false)
82 : {
83 1 : }
84 :
85 : CommandQueue queue; //!< The command->node command queue.
86 : std::string name;
87 : Strings activeLayouts;
88 : ServerSet localServers;
89 : std::string gpuFilter;
90 : float modelUnit;
91 : QApplication* qtApp;
92 : bool running;
93 :
94 1 : void initQt(int argc LB_UNUSED, char** argv LB_UNUSED)
95 : {
96 : #if EQ_GLX_USED || EQ_WGL_USED || EQ_AGL_USED
97 1 : return;
98 : #endif
99 : #ifdef EQ_QT_USED
100 : if (!QApplication::instance())
101 : {
102 : #ifdef __linux__
103 : ::XInitThreads();
104 : #endif
105 : qtApp = new QApplication(argc, argv);
106 : }
107 : #endif
108 : }
109 : };
110 : }
111 :
112 : typedef co::CommandFunc<Client> ClientFunc;
113 :
114 : typedef fabric::Client Super;
115 : /** @endcond */
116 :
117 1 : Client::Client()
118 : : Super()
119 1 : , _impl(new detail::Client)
120 : {
121 2 : registerCommand(fabric::CMD_CLIENT_EXIT,
122 3 : ClientFunc(this, &Client::_cmdExit), &_impl->queue);
123 2 : registerCommand(fabric::CMD_CLIENT_INTERRUPT,
124 3 : ClientFunc(this, &Client::_cmdInterrupt), &_impl->queue);
125 :
126 1 : LBVERB << "New client at " << (void*)this << std::endl;
127 1 : }
128 :
129 2 : Client::~Client()
130 : {
131 1 : LBVERB << "Delete client at " << (void*)this << std::endl;
132 1 : LBASSERT(isClosed());
133 1 : close();
134 1 : delete _impl;
135 1 : }
136 :
137 1 : bool Client::connectServer(ServerPtr server)
138 : {
139 1 : if (Super::connectServer(server))
140 : {
141 0 : server->setClient(this);
142 0 : return true;
143 : }
144 :
145 4 : if (!server->getConnectionDescriptions().empty() ||
146 4 : !Global::getServer().empty() || getenv("EQ_SERVER"))
147 : {
148 0 : return false;
149 : }
150 :
151 : // Use app-local server if no explicit server was set
152 1 : if (!server::startLocalServer(Global::getConfig()))
153 0 : return false;
154 :
155 2 : co::ConnectionPtr connection = server::connectLocalServer();
156 1 : if (!connection || !connect(server, connection))
157 0 : return false;
158 :
159 1 : server->setClient(this);
160 1 : _impl->localServers.insert(server.get());
161 1 : return true;
162 : }
163 :
164 1 : bool Client::disconnectServer(ServerPtr server)
165 : {
166 1 : ServerSet::iterator i = _impl->localServers.find(server.get());
167 1 : if (i == _impl->localServers.end())
168 : {
169 0 : server->setClient(0);
170 0 : const bool success = Super::disconnectServer(server);
171 0 : _impl->queue.flush();
172 0 : return success;
173 : }
174 :
175 : // shut down process-local server (see _startLocalServer)
176 1 : LBASSERT(server->isConnected());
177 1 : const bool success = server->shutdown();
178 1 : server::joinLocalServer();
179 1 : server->setClient(0);
180 :
181 1 : LBASSERT(!server->isConnected());
182 1 : _impl->localServers.erase(i);
183 1 : _impl->queue.flush();
184 1 : return success;
185 : }
186 :
187 : namespace
188 : {
189 : namespace arg = boost::program_options;
190 :
191 1 : arg::options_description _getProgramOptions()
192 : {
193 : arg::options_description options("eq::Client options",
194 1 : lunchbox::term::getSize().first);
195 2 : options.add_options()("eq-client", "Internal, used for render clients")(
196 1 : "eq-layout", arg::value<std::string>(),
197 : "Name of the layout to "
198 : "activate on all canvases during Config::init(). The option can be "
199 2 : "used multiple times.")("eq-gpufilter", arg::value<std::string>(),
200 : "regex selecting the "
201 1 : "hwsd GPUs by name")(
202 1 : "eq-modelunit", arg::value<float>(),
203 : "Scale the rendered models "
204 : "in all views. The model unit defines the size of the model wrt the "
205 : "virtual room unit which is always in meter. The default unit is 1 "
206 1 : "(1 meter or EQ_M).");
207 :
208 1 : return options;
209 : }
210 : }
211 :
212 0 : std::string Client::getHelp()
213 : {
214 0 : std::ostringstream os;
215 0 : os << _getProgramOptions();
216 0 : return os.str();
217 : }
218 :
219 1 : bool Client::initLocal(const int argc, char** argv)
220 : {
221 1 : if (_impl->name.empty() && argc > 0 && argv)
222 : {
223 2 : const boost::filesystem::path prog = argv[0];
224 1 : setName(prog.stem().string());
225 : }
226 :
227 2 : const auto options = _getProgramOptions();
228 2 : arg::variables_map vm;
229 : try
230 : {
231 2 : Strings args;
232 4 : for (int i = 0; i < argc; ++i)
233 3 : if (strcmp(argv[i], "--") != 0)
234 3 : args.push_back(argv[i]);
235 :
236 2 : arg::store(arg::command_line_parser(args)
237 1 : .options(options)
238 1 : .allow_unregistered()
239 2 : .run(),
240 1 : vm);
241 1 : arg::notify(vm);
242 : }
243 0 : catch (const std::exception& e)
244 : {
245 0 : LBERROR << "Error in argument parsing: " << e.what() << std::endl;
246 0 : return false;
247 : }
248 :
249 1 : const bool isClient = vm.count("eq-client");
250 2 : std::string clientOpts;
251 :
252 1 : if (vm.count("eq-layout"))
253 0 : _impl->activeLayouts.push_back(vm["eq-layout"].as<std::string>());
254 1 : if (vm.count("eq-gpufilter"))
255 0 : _impl->gpuFilter = vm["eq-gpufilter"].as<std::string>();
256 1 : if (vm.count("eq-modelunit"))
257 0 : _impl->modelUnit = vm["eq-modelunit"].as<float>();
258 :
259 1 : LBVERB << "Launching " << getNodeID() << std::endl;
260 1 : if (!Super::initLocal(argc, argv))
261 0 : return false;
262 :
263 1 : if (isClient)
264 : {
265 0 : LBVERB << "Client node started from command line with option "
266 0 : << clientOpts << std::endl;
267 :
268 0 : if (!_setupClient(clientOpts))
269 : {
270 0 : exitLocal();
271 0 : return false;
272 : }
273 :
274 0 : _impl->running = true;
275 0 : clientLoop();
276 0 : exitClient();
277 : }
278 :
279 1 : _impl->initQt(argc, argv);
280 1 : return true;
281 : }
282 :
283 0 : bool Client::_setupClient(const std::string& clientArgs)
284 : {
285 0 : LBASSERT(isListening());
286 0 : if (clientArgs.empty())
287 0 : return true;
288 :
289 0 : size_t nextPos = clientArgs.find(CO_SEPARATOR);
290 0 : if (nextPos == std::string::npos)
291 : {
292 0 : LBERROR << "Could not parse working directory: " << clientArgs
293 0 : << std::endl;
294 0 : return false;
295 : }
296 :
297 0 : const std::string workDir = clientArgs.substr(0, nextPos);
298 0 : std::string description = clientArgs.substr(nextPos + 1);
299 :
300 0 : Global::setWorkDir(workDir);
301 0 : if (!workDir.empty() && chdir(workDir.c_str()) == -1)
302 0 : LBWARN << "Can't change working directory to " << workDir << ": "
303 0 : << lunchbox::sysError << std::endl;
304 :
305 0 : nextPos = description.find(CO_SEPARATOR);
306 0 : if (nextPos == std::string::npos)
307 : {
308 0 : LBERROR << "Could not parse server node type: " << description
309 0 : << " is left from " << clientArgs << std::endl;
310 0 : return false;
311 : }
312 :
313 0 : co::NodePtr server = createNode(fabric::NODETYPE_SERVER);
314 0 : if (!server->deserialize(description))
315 0 : LBWARN << "Can't parse server data" << std::endl;
316 :
317 0 : LBASSERTINFO(description.empty(), description);
318 0 : if (!connect(server))
319 : {
320 0 : LBERROR << "Can't connect server node using " << *server << std::endl;
321 0 : return false;
322 : }
323 :
324 0 : return true;
325 : }
326 :
327 0 : void Client::clientLoop()
328 : {
329 0 : LBINFO << "Entered client loop" << std::endl;
330 0 : while (isRunning())
331 0 : processCommand();
332 0 : }
333 :
334 1 : bool Client::exitLocal()
335 : {
336 : #ifdef EQ_QT_USED
337 1 : delete _impl->qtApp;
338 1 : _impl->qtApp = 0;
339 : #endif
340 1 : _impl->activeLayouts.clear();
341 1 : _impl->modelUnit = EQ_UNDEFINED_UNIT;
342 1 : return fabric::Client::exitLocal();
343 : }
344 :
345 0 : void Client::exitClient()
346 : {
347 0 : _impl->queue.flush();
348 0 : bool ret = exitLocal();
349 0 : LBINFO << "Exit " << lunchbox::className(this) << " process used "
350 0 : << getRefCount() << std::endl;
351 :
352 0 : if (!eq::exit())
353 0 : ret = false;
354 0 : ::exit(ret ? EXIT_SUCCESS : EXIT_FAILURE);
355 : }
356 :
357 2 : bool Client::hasCommands()
358 : {
359 2 : return !_impl->queue.isEmpty();
360 : }
361 :
362 0 : bool Client::isRunning() const
363 : {
364 0 : return _impl->running;
365 : }
366 :
367 50 : co::CommandQueue* Client::getMainThreadQueue()
368 : {
369 50 : return &_impl->queue;
370 : }
371 :
372 0 : void Client::addActiveLayout(const std::string& activeLayout)
373 : {
374 0 : _impl->activeLayouts.push_back(activeLayout);
375 0 : }
376 :
377 1 : void Client::setName(const std::string& name)
378 : {
379 1 : _impl->name = name;
380 1 : }
381 :
382 1 : const std::string& Client::getName() const
383 : {
384 1 : return _impl->name;
385 : }
386 :
387 1 : const Strings& Client::getActiveLayouts() const
388 : {
389 1 : return _impl->activeLayouts;
390 : }
391 :
392 1 : const std::string& Client::getGPUFilter() const
393 : {
394 1 : return _impl->gpuFilter;
395 : }
396 :
397 1 : float Client::getModelUnit() const
398 : {
399 1 : return _impl->modelUnit;
400 : }
401 :
402 0 : void Client::interruptMainThread()
403 : {
404 0 : send(fabric::CMD_CLIENT_INTERRUPT);
405 0 : }
406 :
407 0 : co::NodePtr Client::createNode(const uint32_t type)
408 : {
409 0 : switch (type)
410 : {
411 : case fabric::NODETYPE_SERVER:
412 : {
413 0 : Server* server = new Server;
414 0 : server->setClient(this);
415 0 : return server;
416 : }
417 :
418 : default:
419 0 : return Super::createNode(type);
420 : }
421 : }
422 :
423 0 : bool Client::_cmdExit(co::ICommand& command)
424 : {
425 0 : _impl->running = false;
426 : // Close connection here, this is the last command we'll get on it
427 0 : command.getLocalNode()->disconnect(command.getRemoteNode());
428 0 : return true;
429 : }
430 :
431 0 : bool Client::_cmdInterrupt(co::ICommand&)
432 : {
433 0 : return true;
434 : }
435 :
436 : namespace
437 : {
438 1 : class StopNodesVisitor : public ServerVisitor
439 : {
440 : public:
441 1 : virtual ~StopNodesVisitor() {}
442 0 : virtual VisitorResult visitPre(Node* node)
443 : {
444 0 : node->dirtyClientExit();
445 0 : return TRAVERSE_CONTINUE;
446 : }
447 : };
448 : }
449 :
450 1 : void Client::notifyDisconnect(co::NodePtr node)
451 : {
452 1 : if (node->getType() == fabric::NODETYPE_SERVER)
453 : {
454 : // local command dispatching
455 1 : co::OCommand(this, this, fabric::CMD_CLIENT_EXIT, co::COMMANDTYPE_NODE);
456 :
457 2 : ServerPtr server = static_cast<Server*>(node.get());
458 2 : StopNodesVisitor stopNodes;
459 1 : server->accept(stopNodes);
460 : }
461 1 : fabric::Client::notifyDisconnect(node);
462 1 : }
463 30 : }
|