I really want to add Lua Scripting to my Game Engine. I am working with Lua and bound it to C++ using luabind, but I need help to design the way I will construct my Game Entities using Lua.
Engine Information:
Component Oriented, basically each GameEntity is a list of components that are updated in a delta T interval. Basically Game Scenes are composed by collections of Game Entities.
So, here's the dilemma:
Let's say I have this Lua file to define a GameEntity and it's components:
GameEntity =
{
-- Entity Name
"ZombieFighter",
-- All the components that make the entity.
Components =
{
-- Component to define the health of the entity
health =
{
"compHealth", -- Component In-Engine Name
100, -- total health
0.1, -- regeneration rate
},
-- Component to define the Animations of the entity
compAnimation =
{
"compAnimatedSprite",
spritesheet =
{
"spritesheet.gif", -- Spritesheet file name.
80, -- Spritesheet frame width.
80, -- Spritesheet frame height.
},
animations =
{
-- Animation name, Animation spritesheet coords, Animation frame duration.
{"stand", {0,0,1,0,2,0,3,0}, 0.10},
{"walk", {4,0,5,0,6,0,7,0}, 0.10},
{"attack",{8,0,9,0,10,0}, 0.08},
},
},
},
}
As you can see, this GameEntity is defined by 2 components, "compHealth" and "compAnimatedSprite". Those two totally different components require totally different initialization parameters. Health requiring an integer and a float ( total and regeneration ), and on the other side, animations requiring a sprite sheet name , and to define all of the animations ( the frames, durations, etc ).
I would love to make some kind of abstract class with some virtual initializer method that could be used by all my components that require Lua binding in order to facilitate initialization from Lua, but it seems difficult, because the virtual class is not going to have one virtual init method. This is because all of the component initializers (or most of them) require different init parameters (health component requires different init than Animated Sprite component, or AI component).
What do you suggest to make the Lua bindings to the constructors of this components easier ? or how would you do it ?
PS: I must use C++ for this project.
I'd suggest using a composite structure instead for your game entities. Add objects inheriting from a common game entity component to each game entity as you encounter them while parsing the Lua configuration table. This task is a perfect candidate for the factory method. Note that composing your entities in this way still requires all methods to be implemented in the GameEntity class unless you use an alternate dispatch method like message passing (see the visitor pattern)
On the Lua side, I find it is more convenient to use callback function with a single table argument instead of traversing a complex table structure in C/C++. Here is a pure Lua example of what I mean using your data.
-- factory functions
function Health(t) return { total = t[1], regeneration = t[2] } end
function Animation(t) return { spritesheet = t[1], animations = t[2] } end
function Spritesheet(t)
return { filename = t[1], width = t[2], height = t[3] }
end
function Animations(t)
return { name = t[1], coords = t[2], duration = t[3] }
end
-- game entity class prototype and constructor
GameEntity = {}
setmetatable(GameEntity, {
__call = function(self, t)
setmetatable(t, self)
self.__index = self
return t
end,
})
-- example game entity definition
entity = GameEntity{
"ZombieFighter",
Components = {
Health{ 100, 0.1, },
Animation{
Spritesheet{ "spritesheet.gif", 80, 80 },
Animations{
{"stand", {0,0,1,0,2,0,3,0}, 0.10},
{"walk", {4,0,5,0,6,0,7,0}, 0.10},
{"attack", {8,0,9,0,10,0}, 0.08},
},
},
}
}
-- recursively walk the resulting table and print all key-value pairs
function print_table(t, prefix)
prefix = prefix or ''
for k, v in pairs(t) do
print(prefix, k, v)
if 'table' == type(v) then
print_table(v, prefix..'\t')
end
end
end
print_table(entity)
Related
I have a polymorphic (as in arbitrary roles) QObject model that is mostly instantiated declaratively from QML, as in this answer, and I would like to be able to have custom data "views" that sort and filter the model via arbitrary, and potentially - runtime generated from code strings JS functors, something like that:
DataView {
sourceModel: model
filter: function(o) { return o.size > 3 }
sort: function(a, b) { return a.size > b.size }
}
The QSortFilterProxyModel interface doesn't seem to be particularly well suited to the task, instead being fixated on static roles and pre-compiled rules.
I tried using QJSValue properties on the C++ side, but it seems like it is not possible, the C++ code just doesn't compile with that property type. And if I set the property type to QVariant I get error messages from QML that functions can only be bound to var properties. Evidently, var to QVariant conversion doesn't kick in here as it does for return values.
As you mentionned, you could use QJSValue. But that's pretty static. What if you want to use a filter like filter: function(o) { return o.size > slider.value; } with a dynamic slider ? You'll have to manually call invalidateFilter().
As a more practical alternative, you could instead use QQmlScriptString as a property & QQmlExpression to execute it. Using QQmlExpression allows you to be notified of context changes with setNotifyOnValueChanged.
Your syntax would change to be like so : filter: o.size > slider.value.
If you are looking for an out of the box solution, I've implemented this in a library of mine : SortFilterProxyModel on GitHub
You can take a look at ExpressionFilter & ExpressionSorter, those do the same as what you initially wanted. You can check the complete source code in the repo.
How to use it :
import SortFilterProxyModel 0.2
// ...
SortFilterProxyModel {
sourceModel: model
filters: ExpressionFilter { expression: model.size > 3 }
sorters: ExpressionSorter { expression: modelLeft.size < modelRight.size }
}
But as #dtech mentionned, the overhead of going back and forth between qml and c++ for each row of the model is quite noticeable. That's why I created more specific filters and sorters. In your case, we would use RangeFilter and RoleSorter :
import SortFilterProxyModel 0.2
// ...
SortFilterProxyModel {
sourceModel: model
filters: RangeFilter {
roleName: "size"
minimumValue > 3
minimumInclusive: true
}
sorters: RoleSorter { roleName: "size" }
}
Doing like this, we have a nice declarative API and the parameters are only passed once from qml to c++. All the filtering and sorting is then entirely done on the c++ side.
Update:
Revisiting the issue, I finally came with a finalized solution, so I decided to drop in some updates. First, the relevant code:
void set_filter(QJSValue f) {
if (f != m_filter) {
m_filter = f;
filterChanged();
invalidate();
}
}
void set_sorter(QJSValue f) {
if (f != m_sort) {
m_sort = f;
sorterChanged();
sort(0, Qt::DescendingOrder);
}
}
bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const {
if (!m_filter.isCallable()) return true;
QJSValueList l;
l.append(_engine->newQObject(sourceModel()->index(sourceRow, 0, sourceParent).data().value<QObject*>()));
return m_filter.call(l).toBool();
}
bool lessThan(const QModelIndex & left, const QModelIndex & right) const {
if (!m_sort.isCallable()) return false;
QJSValueList l;
l.append(_engine->newQObject(sourceModel()->data(left).value<QObject*>()));
l.append(_engine->newQObject(sourceModel()->data(right).value<QObject*>()));
return m_sort.call(l).toBool();
}
I found this solution to be simpler, safer and better performing than the QQmlScriptString & QQmlExpression duo, which does offer automatic updates on notifications, but as already elaborated in the comments below GrecKo's answer, was kinda flaky and not really worth it.
The hack to get auto-updates for external context property changes is to simply reference them before returning the actual functor:
filter: { expanded; SS.showHidden; o => expanded && (SS.showHidden ? true : !o.hidden) }
Here is a simple expression using the new shorthand function syntax, it references expanded; SS.showHidden; in order to trigger reevaluations if those change, then implicitly returns the functor
o => expanded && (SS.showHidden ? true : !o.hidden)
which is analogous to:
return function(o) { return expanded && (SS.showHidden ? true : !o.hidden) }
which filters out objects based on whether the parent node is expanded, whether the child node is hidden and whether hidden objects are still displayed.
This solution has no way to automatically respond to changes to o.hidden, as o is inserted into the functor upon evaluation and can't be referenced in the binding expression, but this can easily be implemented in the delegates of views that need to dynamically respond to such changes:
Connections {
target: obj
onHiddenChanged: triggerExplicitEvaluation()
}
Remember that the use case involves a schema-less / single QObject* role model that facilitates a metamorphic data model where model item data is implemented via QML properties, so none of the role or regex stock filtering mechanisms are applicable here, but at the same time, this gives the genericity to use a single mechanism to implement sorting and filtering based on any criteria and arbitrary item data, and performance is very good, despite my initial concerns. It doesn't implement a sorting order, that is easily achievable by simply flipping the comparison expression result.
I try to basically implement deep mocking graphs of classes (which I won't control in the wild) in order to record what is being called. The quick and dirty version using simply Mockito was surprisingly robust, but I ran into a wall trying to implement it 'properly' (and not have people ask funny questions why they need mockito in the runtime classpath).
Because this is scala, default constructors are unheard of, null values are very rarely handed gracefuly and deeply nested member classes are a norm, hence when I need to instrument a class, I need to provide actual arguments for the constructor, requiring me to instrument those in turn. Relevant snippets from my code:
private def createTracerClass(tpe :Type, clazz :Class[_]) :Class[_] = {
val name = clazz.getName + TracerClassNameSuffix + tpe.typeSymbol.fullName.replace('.', '_')
val builder =
if (clazz.isInterface) //todo: implement abstract methods!!!
ByteBuddy.subclass(clazz, new ForDefaultConstructor).name(name)
else {
val constructors = clazz.getDeclaredConstructors
.filter { c => (c.getModifiers & (PUBLIC | PROTECTED)) != 0 }.sortBy(_.getParameterCount)
if (constructors.isEmpty)
throw new PropertyReflectionException(
s"Can't instrument a tracer for class ${clazz.getName} as it has no accessible constructor."
)
val best = constructors.head
new ByteBuddy().subclass(clazz, NO_CONSTRUCTORS).name(name)
.defineConstructor(PUBLIC).intercept(
invoke(best).onSuper.`with`(best.getParameterTypes.map(createInstance):_*)
)
}
println("instrumenting " + name + "; class loader: "+clazz.getClassLoader)
val mockClass = builder
.method(not(isConstructor[MethodDescription]())).intercept(to(new MethodInterceptor()))
.defineMethod(TypeMethodName, classOf[AnyRef], PUBLIC).intercept(FixedValue.value(tpe))
.defineField(TraceFieldName, classOf[Trace], PUBLIC)
.make().load(getClassLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT).getLoaded
println("created class " + mockClass +"with classloader: " + mockClass.getClassLoader)
mockClass
}
private def instrumentedInstance(clazz :Class[_], modifiers :Int = PUBLIC | PROTECTED) :AnyRef =
if ((clazz.getModifiers & FINAL) != 0)
null
else {
val mockClass = MockCache.getOrElse(clazz,
clazz.synchronized {
MockCache.getOrElse(clazz, {
println("creating mock class for "+clazz.getName)
clazz.getDeclaredConstructors.filter { c => (c.getModifiers & modifiers) != 0 }
.sortBy(_.getParameterCount).headOption.map { cons =>
val subclass = ByteBuddy.subclass(clazz, NO_CONSTRUCTORS)
.name(clazz.getName + MockClassNameSuffix)
.defineConstructor(PUBLIC).intercept(
invoke(cons).onSuper.`with`(cons.getParameterTypes.map(createInstance) :_*)
).make().load(getClassLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT).getLoaded
MockCache.put(clazz, subclass)
subclass
}.orNull
})
}
)
println("creating a mock for " + clazz.getName + "; class loader: " + mockClass.getClassLoader)
mockClass.getConstructor().newInstance().asInstanceOf[AnyRef]
}
The issue is in the constructor generator at the bottom of the first method, which uses createInstance to create. That method in turn falls back to instrumentInstance.
The result is that I get a NoClassDefFoundError during load (LoadedTypeInitializer$ForStaticField.onLoad()) because each class is loaded with its own class loader. Unfortunately, though the reason was immediately apparent, it helped me not a bit in trying to make ByteBuddy share a class loader or somehow else make those classes available. I played with all provided arguments to make, load but to no avail; having all calls share a TypePool, different type resolution strategies - nothing except the INJECTION ClassLoaderStrategy which I don't want to use due to its reliance on private APIs which wouldn't make investing my effort into this strategical.
It seems like its a very basic issue and simple to solve, but I browsed through many code samples from other projects and I can't see anything they do differently that should make any difference. Ideas?
It's very likely related to your use of ClassLoadingStrategy.Default.WRAPPER_PERSISTENT. Using this strategy, classes are loaded in an isolated class loader that makes classes invisible to anybody not inheriting from that classes class loader.
For loading a group of classes, you'd probably want to combine all unloaded classes (.merge) and load them alltogether in a single class loader.
Note that you can also create a ByteArrayClassLoader yourself and leave it open for injection. This way later classes can be added using the injecting class loading strategy.
I have a polymorphic (as in arbitrary roles) QObject model that is mostly instantiated declaratively from QML, as in this answer, and I would like to be able to have custom data "views" that sort and filter the model via arbitrary, and potentially - runtime generated from code strings JS functors, something like that:
DataView {
sourceModel: model
filter: function(o) { return o.size > 3 }
sort: function(a, b) { return a.size > b.size }
}
The QSortFilterProxyModel interface doesn't seem to be particularly well suited to the task, instead being fixated on static roles and pre-compiled rules.
I tried using QJSValue properties on the C++ side, but it seems like it is not possible, the C++ code just doesn't compile with that property type. And if I set the property type to QVariant I get error messages from QML that functions can only be bound to var properties. Evidently, var to QVariant conversion doesn't kick in here as it does for return values.
As you mentionned, you could use QJSValue. But that's pretty static. What if you want to use a filter like filter: function(o) { return o.size > slider.value; } with a dynamic slider ? You'll have to manually call invalidateFilter().
As a more practical alternative, you could instead use QQmlScriptString as a property & QQmlExpression to execute it. Using QQmlExpression allows you to be notified of context changes with setNotifyOnValueChanged.
Your syntax would change to be like so : filter: o.size > slider.value.
If you are looking for an out of the box solution, I've implemented this in a library of mine : SortFilterProxyModel on GitHub
You can take a look at ExpressionFilter & ExpressionSorter, those do the same as what you initially wanted. You can check the complete source code in the repo.
How to use it :
import SortFilterProxyModel 0.2
// ...
SortFilterProxyModel {
sourceModel: model
filters: ExpressionFilter { expression: model.size > 3 }
sorters: ExpressionSorter { expression: modelLeft.size < modelRight.size }
}
But as #dtech mentionned, the overhead of going back and forth between qml and c++ for each row of the model is quite noticeable. That's why I created more specific filters and sorters. In your case, we would use RangeFilter and RoleSorter :
import SortFilterProxyModel 0.2
// ...
SortFilterProxyModel {
sourceModel: model
filters: RangeFilter {
roleName: "size"
minimumValue > 3
minimumInclusive: true
}
sorters: RoleSorter { roleName: "size" }
}
Doing like this, we have a nice declarative API and the parameters are only passed once from qml to c++. All the filtering and sorting is then entirely done on the c++ side.
Update:
Revisiting the issue, I finally came with a finalized solution, so I decided to drop in some updates. First, the relevant code:
void set_filter(QJSValue f) {
if (f != m_filter) {
m_filter = f;
filterChanged();
invalidate();
}
}
void set_sorter(QJSValue f) {
if (f != m_sort) {
m_sort = f;
sorterChanged();
sort(0, Qt::DescendingOrder);
}
}
bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const {
if (!m_filter.isCallable()) return true;
QJSValueList l;
l.append(_engine->newQObject(sourceModel()->index(sourceRow, 0, sourceParent).data().value<QObject*>()));
return m_filter.call(l).toBool();
}
bool lessThan(const QModelIndex & left, const QModelIndex & right) const {
if (!m_sort.isCallable()) return false;
QJSValueList l;
l.append(_engine->newQObject(sourceModel()->data(left).value<QObject*>()));
l.append(_engine->newQObject(sourceModel()->data(right).value<QObject*>()));
return m_sort.call(l).toBool();
}
I found this solution to be simpler, safer and better performing than the QQmlScriptString & QQmlExpression duo, which does offer automatic updates on notifications, but as already elaborated in the comments below GrecKo's answer, was kinda flaky and not really worth it.
The hack to get auto-updates for external context property changes is to simply reference them before returning the actual functor:
filter: { expanded; SS.showHidden; o => expanded && (SS.showHidden ? true : !o.hidden) }
Here is a simple expression using the new shorthand function syntax, it references expanded; SS.showHidden; in order to trigger reevaluations if those change, then implicitly returns the functor
o => expanded && (SS.showHidden ? true : !o.hidden)
which is analogous to:
return function(o) { return expanded && (SS.showHidden ? true : !o.hidden) }
which filters out objects based on whether the parent node is expanded, whether the child node is hidden and whether hidden objects are still displayed.
This solution has no way to automatically respond to changes to o.hidden, as o is inserted into the functor upon evaluation and can't be referenced in the binding expression, but this can easily be implemented in the delegates of views that need to dynamically respond to such changes:
Connections {
target: obj
onHiddenChanged: triggerExplicitEvaluation()
}
Remember that the use case involves a schema-less / single QObject* role model that facilitates a metamorphic data model where model item data is implemented via QML properties, so none of the role or regex stock filtering mechanisms are applicable here, but at the same time, this gives the genericity to use a single mechanism to implement sorting and filtering based on any criteria and arbitrary item data, and performance is very good, despite my initial concerns. It doesn't implement a sorting order, that is easily achievable by simply flipping the comparison expression result.
I'm looking for a way to dynamically create a component and object, and use the component. It seems like most of the examples available, such as those in the Qt documentation or other StackOverflow posts, are related to using the object returned from createObject(), whereas I want to use the component which contains the (customized) object.
I've stripped out a lot of extraneous detail (e.g. the CustomContainer gets pushed onto/popped off of StackViews), but the following code hopefully illustrates what I'm trying to do... Basically, I would like to have the CustomControl rectangle with foo = 10 and bar = 10, but it seems to load with the defaults instead. There will be multiple "custom control" types and multiple "custom container" objects so I need to be able to support this generically.
The Qt documentation talks about creation contexts, which I assume is my problem, but I'm not sure how to fix this. I'd prefer a purely QML solution, but C++ is fine if that's where the solution lies.
Main.qml:
CustomContainer {
id: myCustomContainer
}
CustomContainer {
id: myOtherCustomContainer
}
function addCustomControl( control, args ) {
var newComponent = Qt.createComponent( control )
var newObj = newComponent.createObject( myCustomContainer, args )
return newComponent
}
myCustomContainer.loaderSource = addCustomControl( "CustomControl.qml", { "foo": 10, "bar": 10 } )
myOtherCustomContainer.loaderSource = addCustomControl( "CustomControl.qml", { "foo": 20, "bar": 20 } )
CustomControl.qml:
Rectangle {
property int foo: 5
property int bar: 5
}
CustomContainer.qml:
Item {
property Component loaderSource
onLoaderSourceChanged: {
myLoader.sourceComponent = loaderSource
}
Loader {
id: myLoader
onSourceComponentChanged: {
doStuff()
}
}
}
The component does not "contain the object". The component is a prototype for objects to be instantiated. Think of it like a "type" or a class or struct in C++ vs an instance of that type.
Your code creates the component, and then creates an object from it with modified values for the properties, but the component still has its default properties, so using it as a source component will produce objects with default properties.
Furthermore, a Loader will do automatic dynamic instantiation for you. So you don't need to combine both manual and automatic, either do it manually, or leave the loader to do it.
Last, but not least, when components are instantiated by a StackView they will automatically fill it and their size will be bound to it, so it will automatically change as the StackView size changes. So just use an Item and put your content in there and layout it. Only the root item's size will be bound to the StackView size, its children items will have their own sizes.
I am relatively new to using MSpec and as I write more and more tests it becomes obvious to reduce duplication you often have to use a base class for your setup as per Rob Conery's article
I am happy with using the AssertWasCalled method to verify my expectations, but where do you set up a stub's return value, I find it useful to set the context in the base class injecting my dependencies but that (I think) means that I need to set my stubs up in the Because delegate which just feels wrong.
Is there a better approach I am missing?
The initialization/setup of stubs belongs to the arrange phase. The arrange phase is used to get the system into a known state before you exercise it.
In MSpec, the arrange phase is performed in Establish fields. For example:
public class When_the_temperature_threshold_is_reached
{
static ITemperatureSensor Sensor;
static Threshold Threshold;
Establish context = () =>
{
Sensor = MockRepository.GenerateStub<ITemperatureSensor>();
Sensor
.Stub(x => x.GetTemperature())
.Return(42);
Threshold = new Threshold(Sensor);
};
Because of = () => Reached = Threshold.IsReached(40);
It should_report_that_the_threshold_was_reached =
() => Reached.ShouldBeTrue();
}
When you write more tests using that kind of ITemperatureSensor, you should extract a base class that does complicated or repeated setup.
public abstract class TemperatureSpecs
{
protected static ITemperatureSensor CreateSensorAlwaysReporting(int temperature)
{
var sensor = MockRepository.GenerateStub<ITemperatureSensor>();
sensor
.Stub(x => x.GetTemperature())
.Return(temperature);
return sensor;
}
}
public class When_the_temperature_threshold_is_reached : TemperatureSpecs
{
// Everything else cut for brevity.
Establish context = () =>
{
Sensor = CreateSensorAlwaysReporting(42);
Threshold = new Threshold(Sensor);
};
}
This gives you the advantage that you can influence the stub's return value from the context itself: You do this by keeping as much information as possible local to the context and provide a good name for the "setup" method in the base class.
There is no need to specifiy or expect anything stub-related in Because. When Because is run, your system should be in a state where it can be exercised without further preparation.