qtscript: Add debug window showing globals, triggers and labels.

master
per 2013-02-02 14:51:56 +01:00
parent aa213528c0
commit eb21e8e847
10 changed files with 465 additions and 17 deletions

View File

@ -107,7 +107,7 @@ if test "$backend" = "qt"; then
PKG_CHECK_MODULES(QT4, QtCore QtGui QtOpenGL QtNetwork QtScript,,[:])
else
AC_MSG_RESULT([SDL])
PKG_CHECK_MODULES(QT4, QtCore QtNetwork QtScript,,[:])
PKG_CHECK_MODULES(QT4, QtCore QtGui QtNetwork QtScript,,[:])
fi
if test "$pkg_failed" = "yes" ; then
AC_MSG_ERROR([Qt not found - required!])

View File

@ -22,7 +22,7 @@
* SDL backend code
*/
#include <QtCore/QCoreApplication>
#include <QtGui/QApplication>
#include "lib/framework/wzapp.h"
#include "lib/framework/input.h"
#include "lib/framework/utf.h"
@ -1087,7 +1087,7 @@ void wzMain(int &argc, char **argv)
{
initKeycodes();
appPtr = new QCoreApplication(argc, argv); // For Qt-script.
appPtr = new QApplication(argc, argv); // For Qt-script.
}
bool wzMain2()
@ -1296,6 +1296,9 @@ void wzMain3()
break;
}
}
appPtr->processEvents();
mainLoop();
inputNewFrame();
}

View File

@ -4,6 +4,12 @@ AM_CXXFLAGS = $(WZ_CXXFLAGS) $(QT4_CFLAGS)
AM_LFLAGS = $(FLEX_FLAGS)
AM_YFLAGS = -d
MOCHEADER = qtscriptdebug.h
MOCEDFILES = $(MOCHEADER:%.h=%_moc.cpp)
%_moc.cpp: %.h
$(MOC4) -o $@ $<
.PHONY: autorevision.h
autorevision.h autorevision.cache:
@ -13,7 +19,8 @@ autorevision.h autorevision.cache:
CLEANFILES = \
$(BUILT_SOURCES) \
autorevision.cache \
autorevision.h.new
autorevision.h.new \
$(MOCEDFILES)
BUILT_SOURCES = \
autorevision.h \
@ -118,6 +125,7 @@ noinst_HEADERS = \
projectiledef.h \
projectile.h \
qtscript.h \
qtscriptdebug.h \
qtscriptfuncs.h \
radar.h \
random.h \
@ -150,7 +158,11 @@ noinst_HEADERS = \
warzoneconfig.h \
wavecast.h \
weapondef.h \
wrappers.h
wrappers.h \
$(MOCHEADER)
nodist_warzone2100_SOURCES = \
qtscriptdebug_moc.cpp
warzone2100_SOURCES = \
action.cpp \
@ -231,6 +243,7 @@ warzone2100_SOURCES = \
power.cpp \
projectile.cpp \
qtscript.cpp \
qtscriptdebug.cpp \
qtscriptfuncs.cpp \
radar.cpp \
random.cpp \

View File

@ -42,6 +42,7 @@ struct CHEAT_ENTRY
bool Cheated = false;
static CHEAT_ENTRY cheatCodes[] =
{
{"jsdebug", jsShowDebug}, // show scripting states
{"clone wars", kf_CloneSelected}, // clone selected units
{"noassert", kf_NoAssert}, // turn off asserts
{"count me", kf_ShowNumObjects}, // give a count of objects in the world

View File

@ -34,6 +34,7 @@
#include <QtCore/QString>
#include <QtCore/QStringList>
#include <QtCore/QFileInfo>
#include <QtGui/QStandardItemModel>
#include "lib/framework/wzapp.h"
#include "lib/framework/wzconfig.h"
@ -44,6 +45,7 @@
#include "difficulty.h"
#include "lib/netplay/netplay.h"
#include "qtscriptdebug.h"
#include "qtscriptfuncs.h"
#define ATTACK_THROTTLE 1000
@ -67,10 +69,11 @@ struct timerNode
int frameTime;
int ms;
int player;
int calls;
timerType type;
timerNode() {}
timerNode(QScriptEngine *caller, QString val, int plr, int frame)
: function(val), engine(caller), baseobj(-1), frameTime(frame + gameTime), ms(frame), player(plr), type(TIMER_REPEAT) {}
: function(val), engine(caller), baseobj(-1), frameTime(frame + gameTime), ms(frame), player(plr), calls(0), type(TIMER_REPEAT) {}
bool operator== (const timerNode &t) { return function == t.function && player == t.player; }
// implement operator less TODO
};
@ -102,6 +105,14 @@ typedef struct monitor_bin
typedef QHash<QString, MONITOR_BIN> MONITOR;
static QHash<QScriptEngine *, MONITOR *> monitors;
static MODELMAP models;
static QStandardItemModel *triggerModel;
static bool globalDialog = false;
static void updateGlobalModels();
// ----------------------------------------------------------
// Call a function by name
static bool callFunction(QScriptEngine *engine, const QString &function, const QScriptValueList &args, bool required = false)
{
@ -331,6 +342,10 @@ bool initScripts()
bool shutdownScripts()
{
jsDebugShutdown();
globalDialog = false;
models.clear();
triggerModel = NULL;
for (int i = 0; i < scripts.size(); ++i)
{
QScriptEngine *engine = scripts.at(i);
@ -403,7 +418,6 @@ bool updateScripts()
// TODO - load balancing
QList<timerNode> runlist; // make a new list here, since we might trample all over the timer list during execution
QList<timerNode>::iterator iter;
// Weed out dead timers
for (iter = timers.begin(); iter != timers.end(); iter++)
{
if (iter->frameTime <= gameTime)
@ -413,6 +427,7 @@ bool updateScripts()
{
iter->type = TIMER_ONESHOT_DONE; // unless there is none
}
iter->calls++;
runlist.append(*iter);
}
}
@ -429,6 +444,12 @@ bool updateScripts()
}
callFunction(iter->engine, iter->function, args, true);
}
if (globalDialog)
{
updateGlobalModels();
}
return true;
}
@ -562,7 +583,7 @@ bool saveScriptStates(const char *filename)
ini.setValue("me", node.player);
ini.setValue("scriptName", node.engine->globalObject().property("scriptName").toString());
ini.setValue("function", node.function);
if (!node.baseobj >= 0)
if (node.baseobj >= 0)
{
ini.setValue("object", QVariant(node.baseobj));
}
@ -646,6 +667,91 @@ bool loadScriptStates(const char *filename)
return true;
}
static void updateGlobalModels()
{
for (int i = 0; i < scripts.size(); ++i)
{
QScriptEngine *engine = scripts.at(i);
QScriptValueIterator it(engine->globalObject());
QStandardItemModel *m = models.value(engine);
m->setRowCount(0);
while (it.hasNext())
{
it.next();
if (!internalNamespace.contains(it.name()) && !it.value().isFunction())
{
int nextRow = m->rowCount();
m->setRowCount(nextRow);
m->setItem(nextRow, 0, new QStandardItem(it.name()));
m->setItem(nextRow, 1, new QStandardItem(it.value().toString()));
}
}
}
QStandardItemModel *m = triggerModel;
m->setRowCount(0);
for (int i = 0; i < timers.size(); ++i)
{
timerNode node = timers.at(i);
int nextRow = m->rowCount();
m->setRowCount(nextRow);
m->setItem(nextRow, 0, new QStandardItem(node.function));
m->setItem(nextRow, 1, new QStandardItem(node.player));
QString scriptName = node.engine->globalObject().property("scriptName").toString();
m->setItem(nextRow, 2, new QStandardItem(scriptName + ":" + QString::number(node.player)));
if (node.baseobj >= 0)
{
m->setItem(nextRow, 3, new QStandardItem(QString::number(node.baseobj)));
}
else
{
m->setItem(nextRow, 3, new QStandardItem("-"));
}
m->setItem(nextRow, 4, new QStandardItem(QString::number(node.frameTime)));
m->setItem(nextRow, 5, new QStandardItem(QString::number(node.ms)));
if (node.type == TIMER_ONESHOT_READY)
{
m->setItem(nextRow, 6, new QStandardItem("Oneshot"));
}
else if (node.type == TIMER_ONESHOT_DONE)
{
m->setItem(nextRow, 6, new QStandardItem("Done"));
}
else
{
m->setItem(nextRow, 6, new QStandardItem("Repeat"));
}
m->setItem(nextRow, 7, new QStandardItem(QString::number(node.calls)));
}
}
void jsShowDebug()
{
// Add globals
for (int i = 0; i < scripts.size(); ++i)
{
QScriptEngine *engine = scripts.at(i);
QStandardItemModel *m = new QStandardItemModel(0, 2);
m->setHeaderData(0, Qt::Horizontal, QString("Name"));
m->setHeaderData(1, Qt::Horizontal, QString("Value"));
models.insert(engine, m);
}
// Add triggers
triggerModel = new QStandardItemModel(0, 8);
triggerModel->setHeaderData(0, Qt::Horizontal, QString("Function"));
triggerModel->setHeaderData(1, Qt::Horizontal, QString("Player"));
triggerModel->setHeaderData(2, Qt::Horizontal, QString("Script"));
triggerModel->setHeaderData(3, Qt::Horizontal, QString("Object"));
triggerModel->setHeaderData(4, Qt::Horizontal, QString("Time"));
triggerModel->setHeaderData(5, Qt::Horizontal, QString("Interval"));
triggerModel->setHeaderData(6, Qt::Horizontal, QString("Type"));
triggerModel->setHeaderData(7, Qt::Horizontal, QString("Calls"));
globalDialog = true;
updateGlobalModels();
jsDebugCreate(models, triggerModel);
}
// ----------------------------------------------------------------------------------------
// Events

View File

@ -78,6 +78,8 @@ bool writeLabels(const char *filename);
/// Tell script system that an object has been removed.
void scriptRemoveObject(BASE_OBJECT *psObj);
void jsShowDebug();
// ----------------------------------------------
// Event functions

132
src/qtscriptdebug.cpp Normal file
View File

@ -0,0 +1,132 @@
/*
This file is part of Warzone 2100.
Copyright (C) 2013 Warzone 2100 Project
Warzone 2100 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
(at your option) any later version.
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file qtscriptdebug.cpp
*
* New scripting system - debug GUI
*/
#include "qtscriptdebug.h"
#include <QtCore/QHash>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptValueIterator>
#include <QtCore/QList>
#include <QtCore/QString>
#include <QtCore/QStringList>
#include <QtGui/QDialog>
#include <QtGui/QTreeView>
#include <QtGui/QTabWidget>
#include <QtGui/QPushButton>
#include <QtGui/QHBoxLayout>
#include <QtGui/QVBoxLayout>
#include <QtGui/QStandardItemModel>
#include "lib/framework/wzapp.h"
#include "lib/framework/wzconfig.h"
#include "qtscript.h"
#include "qtscriptfuncs.h"
static ScriptDebugger *globalDialog = NULL;
// ----------------------------------------------------------
ScriptDebugger::ScriptDebugger(const MODELMAP &models, QStandardItemModel *triggerModel) : QDialog(NULL, Qt::Window)
{
// Add globals
for (MODELMAP::const_iterator i = models.constBegin(); i != models.constEnd(); i++)
{
QScriptEngine *engine = i.key();
QStandardItemModel *m = i.value();
QTreeView *view = new QTreeView();
view->setSelectionMode(QAbstractItemView::NoSelection);
view->setModel(m);
QString scriptName = engine->globalObject().property("scriptName").toString();
int player = engine->globalObject().property("me").toInt32();
tab.addTab(view, scriptName + ":" + QString::number(player));
}
// Add triggers
triggerView.setModel(triggerModel);
triggerView.resizeColumnToContents(0);
triggerView.setSelectionMode(QAbstractItemView::NoSelection);
triggerView.setSelectionBehavior(QAbstractItemView::SelectRows);
tab.addTab(&triggerView, "Triggers");
// Add labels
labelModel = createLabelModel();
labelView.setModel(labelModel);
labelView.resizeColumnToContents(0);
labelView.setSelectionMode(QAbstractItemView::SingleSelection);
labelView.setSelectionBehavior(QAbstractItemView::SelectRows);
connect(&labelView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(labelClickedIdx(const QModelIndex &)));
QPushButton *button = new QPushButton("Show", this);
connect(button, SIGNAL(pressed()), this, SLOT(labelClicked()));
QVBoxLayout *labelLayout = new QVBoxLayout(this);
labelLayout->addWidget(&labelView);
labelLayout->addWidget(button);
QWidget *dummyWidget = new QWidget(this);
dummyWidget->setLayout(labelLayout);
tab.addTab(dummyWidget, "Labels");
// Set up dialog
QHBoxLayout *layout = new QHBoxLayout(this);
layout->addWidget(&tab);
setLayout(layout);
setSizeGripEnabled(true);
show();
raise();
activateWindow();
}
void ScriptDebugger::labelClicked()
{
QItemSelectionModel *selected = labelView.selectionModel();
QModelIndex idx = selected->currentIndex();
QStandardItem *item = labelModel->itemFromIndex(labelModel->index(idx.row(), 0));
showLabel(item->text());
}
void ScriptDebugger::labelClickedIdx(const QModelIndex &idx)
{
QStandardItem *item = labelModel->itemFromIndex(labelModel->index(idx.row(), 0));
showLabel(item->text());
}
ScriptDebugger::~ScriptDebugger()
{
}
bool jsDebugShutdown()
{
delete globalDialog;
globalDialog = NULL;
return true;
}
void jsDebugCreate(const MODELMAP &models, QStandardItemModel *triggerModel)
{
if (globalDialog)
{
delete globalDialog;
}
globalDialog = new ScriptDebugger(models, triggerModel);
}

63
src/qtscriptdebug.h Normal file
View File

@ -0,0 +1,63 @@
/*
This file is part of Warzone 2100.
Copyright (C) 2013 Warzone 2100 Project
Warzone 2100 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
(at your option) any later version.
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef __INCLUDED_QTSCRIPTDEBUG_H__
#define __INCLUDED_QTSCRIPTDEBUG_H__
#include "lib/framework/frame.h"
#include "basedef.h"
#include "droiddef.h"
#include "structuredef.h"
#include "researchdef.h"
#include "featuredef.h"
class QScriptEngine;
class QStandardItemModel;
class QModelIndex;
#include <QtCore/QHash>
#include <QtGui/QDialog>
#include <QtGui/QTableWidget>
#include <QtGui/QTreeView>
typedef QHash<QScriptEngine *, QStandardItemModel *> MODELMAP;
class ScriptDebugger : public QDialog
{
Q_OBJECT
public:
ScriptDebugger(const MODELMAP &models, QStandardItemModel *triggerModel);
~ScriptDebugger();
private:
QTabWidget tab;
QStandardItemModel *labelModel;
QTreeView labelView;
QTreeView triggerView;
protected slots:
void labelClickedIdx(const QModelIndex &idx);
void labelClicked();
};
void jsDebugCreate(const MODELMAP &models, QStandardItemModel *triggerModel);
bool jsDebugShutdown();
#endif

View File

@ -31,6 +31,7 @@
#include <QtScript/QScriptValue>
#include <QtCore/QStringList>
#include <QtGui/QStandardItemModel>
#include "action.h"
#include "console.h"
@ -90,6 +91,7 @@ struct labeltype
};
typedef QMap<QString, labeltype> LABELMAP;
static LABELMAP labels;
static QStandardItemModel *labelModel = NULL;
#define SCRIPT_ASSERT_PLAYER(_context, _player) \
SCRIPT_ASSERT(_context, _player >= 0 && _player < MAX_PLAYERS, "Invalid player index %d", _player);
@ -97,6 +99,127 @@ static LABELMAP labels;
// ----------------------------------------------------------------------------------------
// Utility functions -- not called directly from scripts
static void updateLabelModel()
{
if (!labelModel)
{
return;
}
labelModel->setRowCount(0);
labelModel->setRowCount(labels.size());
int nextRow = 0;
for (LABELMAP::iterator i = labels.begin(); i != labels.end(); i++)
{
labeltype &l = (*i);
labelModel->setItem(nextRow, 0, new QStandardItem(i.key()));
const char *c = "?";
switch (l.type)
{
case OBJ_DROID: c = "DROID"; break;
case OBJ_FEATURE: c = "FEATURE"; break;
case OBJ_STRUCTURE: c = "STRUCTURE"; break;
case SCRIPT_POSITION: c = "POSITION"; break;
case SCRIPT_AREA: c = "AREA"; break;
case SCRIPT_GROUP: c = "GROUP"; break;
case SCRIPT_PLAYER:
case SCRIPT_RESEARCH:
case OBJ_PROJECTILE:
case OBJ_TARGET:
case SCRIPT_COUNT: c = "ERROR"; break;
}
labelModel->setItem(nextRow, 1, new QStandardItem(QString(c)));
nextRow++;
}
}
QStandardItemModel *createLabelModel()
{
if (labelModel)
{
delete labelModel;
}
labelModel = new QStandardItemModel(0, 2);
labelModel->setHeaderData(0, Qt::Horizontal, QString("Label"));
labelModel->setHeaderData(1, Qt::Horizontal, QString("Type"));
updateLabelModel();
return labelModel;
}
static void clearMarks()
{
for (int x = 0; x < mapWidth; x++) // clear old marks
{
for (int y = 0; y < mapHeight; y++)
{
MAPTILE *psTile = mapTile(x, y);
psTile->tileInfoBits &= ~BITS_MARKED;
}
}
}
void showLabel(const QString &key)
{
if (!labels.contains(key))
{
debug(LOG_ERROR, "label %s not found", key.toUtf8().constData());
return;
}
labeltype &l = labels[key];
if (l.type == SCRIPT_AREA || l.type == SCRIPT_POSITION)
{
setViewPos(map_coord(l.p1.x), map_coord(l.p1.y), false); // move camera position
clearMarks();
int maxx = map_coord(l.p2.x);
int maxy = map_coord(l.p2.y);
if (l.type == SCRIPT_POSITION)
{
maxx = MIN(mapWidth, maxx + 1);
maxy = MIN(mapHeight, maxy + 1);
}
for (int x = map_coord(l.p1.x); x < maxx; x++) // make new ones
{
for (int y = map_coord(l.p1.y); y < maxy; y++)
{
MAPTILE *psTile = mapTile(x, y);
psTile->tileInfoBits |= BITS_MARKED;
}
}
}
else if (l.type == OBJ_DROID || l.type == OBJ_FEATURE || l.type == OBJ_STRUCTURE)
{
clearMarks();
BASE_OBJECT *psObj = IdToObject((OBJECT_TYPE)l.type, l.id, l.player);
if (psObj)
{
setViewPos(map_coord(psObj->pos.x), map_coord(psObj->pos.y), false); // move camera position
MAPTILE *psTile = mapTile(map_coord(psObj->pos.x), map_coord(psObj->pos.y));
psTile->tileInfoBits |= BITS_MARKED;
}
}
else if (l.type == SCRIPT_GROUP)
{
bool cameraMoved = false;
clearMarks();
for (ENGINEMAP::iterator i = groups.begin(); i != groups.end(); ++i)
{
for (GROUPMAP::const_iterator iter = i.value()->constBegin(); iter != i.value()->constEnd(); ++iter)
{
if (iter.value() == l.id)
{
BASE_OBJECT *psObj = iter.key();
if (!cameraMoved)
{
setViewPos(map_coord(psObj->pos.x), map_coord(psObj->pos.y), false); // move camera position
cameraMoved = true;
}
MAPTILE *psTile = mapTile(map_coord(psObj->pos.x), map_coord(psObj->pos.y));
psTile->tileInfoBits |= BITS_MARKED;
}
}
}
}
}
bool areaLabelCheck(DROID *psDroid)
{
int x = psDroid->pos.x;
@ -808,6 +931,7 @@ static QScriptValue js_addLabel(QScriptContext *context, QScriptEngine *engine)
}
QString key = context->argument(1).toString();
labels.insert(key, value);
updateLabelModel();
return QScriptValue();
}
@ -818,6 +942,7 @@ static QScriptValue js_removeLabel(QScriptContext *context, QScriptEngine *engin
{
QString key = context->argument(0).toString();
int result = labels.remove(key);
updateLabelModel();
return QScriptValue(result);
}
@ -3615,14 +3740,7 @@ static QScriptValue js_hackMarkTiles(QScriptContext *context, QScriptEngine *)
}
else // clear all marks
{
for (int x = 0; x < mapWidth; x++)
{
for (int y = 0; y < mapHeight; y++)
{
MAPTILE *psTile = mapTile(x, y);
psTile->tileInfoBits &= ~BITS_MARKED;
}
}
clearMarks();
}
return QScriptValue();
}
@ -3691,6 +3809,8 @@ bool unregisterFunctions(QScriptEngine *engine)
delete psMap;
ASSERT(num == 1, "Number of engines removed from group map is %d!", num);
labels.clear();
delete labelModel;
labelModel = NULL;
return true;
}
@ -3722,6 +3842,7 @@ void prepareLabels()
}
}
}
updateLabelModel();
}
bool registerFunctions(QScriptEngine *engine, QString scriptName)

View File

@ -20,11 +20,12 @@
#ifndef __INCLUDED_QTSCRIPTFUNCS_H__
#define __INCLUDED_QTSCRIPTFUNCS_H__
#include "lib/framework/frame.h"
#include "qtscript.h"
#include "featuredef.h"
class QStandardItemModel;
enum SCRIPT_TYPE
{
SCRIPT_POSITION = OBJ_NUM_TYPES,
@ -68,6 +69,12 @@ void dumpScriptLog(const QString &scriptName, int me, const QString &info);
// Script functions useful also in qtscript.cpp
QScriptValue js_enumSelected(QScriptContext *, QScriptEngine *engine);
/// Create model for labels for js debug dialog
QStandardItemModel *createLabelModel();
/// Mark and show label
void showLabel(const QString &key);
/// Assert for scripts that give useful backtraces and other info.
#define SCRIPT_ASSERT(context, expr, ...) \
do { bool _wzeval = (expr); \