How to schedule or call a method at random time interval in cocos2d iphone - cocos2d-iphone

I want to call a method at Irregular time interval means it should be random time plus i want it in some define range too.
Like : it should call at any second between 3 to 8 .
I tried this one :
[NSTimer scheduledTimerWithInterval: 1.0 target:self selector:#selector(myMethod:) userInfo:nil repeats: YES];
void mymethod()
{
if(arc4random() % 10 == 1)
{
// calling my method here;
}
}
This way , i am not getting randomization which i want.
Any one can please help me on this !!!

Here you can make a scheduler which will get called at random interval.
-(void)randomTimeScheduler{
int time = arc4random()%5;
int nextTimeOfCall = 3+time;
NSLog("it will be called after:%d",nextTimeOfCall);
[self performSelector:#selector(randomTimeScheduler) withObject:self afterDelay:nextTimeOfCall];
}
You have to call it from your class and then it will work as a scheduler. And it has finite interval time 3-8.

Related

Updating QChart from QLineSeries in a running while loop

I want to make my QChart dynamically update whenever a point is added to the QLineSeries object attached to it, but it seems that this update only occurs after the while loop I am running has finished. I am using said while loop in interface.cpp that calls a function updatePlot() which adds the data point to the line series, but this only updates the chart after the while loop has completely finished. Pseudo code of what is happening here:
qtwindow.cpp
// Constructor that initializes the series which will be passed into the interface
AlgoWindow::AlgoWindow( ..., TradingInterface* interface, ... ) {
...
QLineSeries* series = new QLineSeries();
QLineSeries* benchmark = new QLineSeries();
QChart* chart = new QChart();
chart->addSeries(series);
chart->addSeries(benchmark);
// Also creates custom axes which are attached to each series
...
}
// Slot connected to a button signal
void AlgoWindow::buttonClicked() {
// Runs the backtest
interface->runbacktest(..., series, benchmark, ...);
}
interface.cpp
void TradingInterface::runbacktest(..., QtCharts::QLineSeries* algoplot, QtCharts::QLineSeries* benchplot) {
// Runs a huge while loop that continuously checks for events
while (continue_backtest) {
if (!eventsqueue.isEmpty()) {
// Handle each event for the bar
} else {
// All events have been handled for the day, so plot
updatePlot(algoplot, benchplot);
}
}
}
void TradingInterface::updatePlot(QtCharts::QLineSeries *algoseries,
QtCharts::QLineSeries *benchseries) {
// Get the date and the information to put in each point
long date = portfolio.bars->latestDates.back();
double equitycurve = portfolio.all_holdings.rbegin().operator*().second["equitycurve"];
double benchcurve = benchmarkportfolio.all_holdings.rbegin().operator*.second["equitycurve"];
// Append the new points to their respective QLineSeries
algoseries->append(date * 1000, equitycurve*100);
benchseries->append(date * 1000, benchcurve*100);
}
This gives me no errors and the while loop completes, but the lines are only plotted after runbacktest() exits. It then plots all the data correctly, but all at once.
What I need to happen is for the QChart to update every time the lines are added, which my guess was to use some form of custom signal-slot listener but I have no clue how to go about that. If the graph will not update until after the function completes, is it even possible within the QChart framework?
Also, I have already tried QChart::update() and QChartView::repaint(). Both produced the same results as without.
EDIT: I tried setting up a new thread that emits a signal back to the main thread whenever the data is completed but it seems to have changed nothing. The QChart still does not update until after all the data has been inputted. I added a couple lines to help debug and it seems like the function which emits the signal runs consistently just fine, but the slot function which receives the signal only runs after the thread has finished. Not only that, but slowing the signals down with a sleep does not make it plot slowly (like I thought), as the QChart still refuses to update until after the final update to addData().
Either remove your while loop and perform the work one step at a time with a timer.
Or run your runbacktest function in another thread and send a signal to update the QChart in the UI's thread when the data is ready.
Either way you need to give control back to the event loop so that the chart can be repainted.
The Qt idiom for running an operation “continuously” is to use a zero-duration “timer”. It’s not a timer really, but Qt calls it one.
You can do the operation in chunks that take approximately a millisecond. For this, invert the control flow. Qt doesn't provide too much syntactic sugar for it, but it's easy to remedy.
Convert this code, which maintains a loop:
for (int i = 0; i < 1000; ++i) {
doSomething(i);
}
into this lambda, which is invoked by the event loop:
m_tasks.addTask([this](i = 0) mutable {
doSomething(i);
++i;
return i < 1000;
});
assuming:
class Controller : public QObject {
Tasks m_tasks;
...
};
where the Tasks class maintains a list of tasks to be executed by the event loop:
class Tasks : public QObject {
Q_OBJECT
QBasicTimer timer;
std::list<std::function<bool()>> tasks;
protected:
void timerEvent(QTimerEvent *ev) override {
if (ev->timerId() != timer.timerId())
return;
for (auto it = tasks.begin(); it != tasks.end(); ) {
bool keep = (*it)();
if (!keep)
it = tasks.erase(it);
else
++it;
}
if (tasks.empty())
timer.stop();
}
public:
using QObject :: QObject;
template <typename F> void addTask(F &&fun) {
tasks.emplace_back(std::forward(fun));
if (!timer.isActive())
timer.start(0, this);
}
};

Pause/Resume Action/Animation on Sprite in Cocos2d

Using Cocos2d-x and C++, I'm trying to play and pause an animation for a sprite.
I'm using version 3.15.1 of Cocos2dx.
I have a class called PlayerSprite which is derrived from the cocos2d::Sprite class. Inside PlayerSprite initialization, I've setup my animation with the following code:
SpriteBatchNode* playerSpriteBatch = SpriteBatchNode::create("player.png");
SpriteFrameCache* spriteFrameCache = SpriteFrameCache::getInstance();
spriteFrameCache->addSpriteFramesWithFile("player.plist");
Vector<SpriteFrame*> animFrames(2);
char str[18] = { 0 };
for (int i = 1; i < 3; i++) {
sprintf(str, "player_idle_%d.png", i);
SpriteFrame* frame = spriteFrameCache->getSpriteFrameByName(str);
animFrames.pushBack(frame);
}
Animation* idleAnim = Animation::createWithSpriteFrames(animFrames, 0.8f);
self->idleAction = self->runAction(RepeatForever::create(Animate::create(idleAnim)));
self->idleAction->setTag(0);
When I run the code, it works fine and the animation loops correctly.
In my void update() method, I am trying to pause/play the action/animation based of weather the player is moving or idle.
I do this with the following code:
const bool isIdleActionRunning = this->getNumberOfRunningActionsByTag(0) > 0 ? true : false;
const bool isMoving = !vel.isZero();
if (!isMoving && !isIdleActionRunning) {
// Player is idle AND animation is not running
// Run animation
this->runAction(idleAction);
} else if (isMoving && isIdleActionRunning) {
// Player is moving but animation is running
// Pause animation
this->stopActionByTag(0);
}
When I run this code now, my character falls, and as soon as he hits the gound, I get an error at this->runAction(idleAction); saying:
Access violation reading location 0xDDDDDDE5
I believe this is caused due to this->stopActionByTag(0) deleting the action pointer. I've tried to clone the action to avoid this but have had no success.
I know this is a bit late and you might already have solved this but here goes...
Your problem is that you cannot use one instance of Action (idleAction) multiple times. So, once you have run it and removed it, it is released and cannot be used. So, you have 2 options now,
Either create a new idleAction Action every time before running the action.
Or, have an idleAction retained and don't run it ever. Instead, create a clone of this idleAction and run a new clone each time. i.e.
idleAction->retain();
const bool isIdleActionRunning = this->getNumberOfRunningActionsByTag(0) > 0 ? true : false;
const bool isMoving = !vel.isZero();
if (!isMoving && !isIdleActionRunning) {
// Player is idle AND animation is not running
// Run animation
Action idleActionClone = idleAction->clone();
this->runAction(idleActionClone);
} else if (isMoving && isIdleActionRunning) {
// Player is moving but animation is running
// Pause animation
this->stopActionByTag(0);
}
Solution: call retain() to keep your action.
It's a matter of memory management of cocos2d-x.
In create() function of your RepeatForever class (derived from Ref), the reference count is set to 1 and there is a line of code ret->autorelease() before returning the object, which means this object will be released automatically at the end of this frame.
You called runAction() function the same frame you created it, the action is retained by ActionManager, it's reference count set to 2, and then 1 at the end of the frame (autorelease).
After your stopping it, it's released by ActionManager, reference count set to 0 and it's deleted. After this you use your action, it will be an access violation method.
*Edit: don't forget to release the action manually when PlayerSprite is deleted, or it's a leak.
When you stop action it's being recycled from memory. In order to play action once more, you have to recreate it. So you may just make a creator function, which returns your animation. The downside is you're recreating animation each time and it'll also play from the beginning (technically you can rewind it).
But I've developed a simpler technique to pause/resume animations:
Let's say you have an action:
action = MoveBy::create(5.0f, Vec2(50, 100));
Now, you can embed this action into Speed action like this:
action = Speed::create(MoveBy::create(5.0f, Vec2(50, 100)), 1.0f);
1.0f - is speed, so it's normal action rate. Now to pause just call:
action->setSpeed(0.0f);
and to resume:
action->setSpeed(1.0f);
you can also use different speed if you need it for some reason or another ;)

passing a current time variable

I am trying to get a text edit box to display the current time every 5 seconds using QTimer. I am having the current time figured in a separate method and then having the QTimer call that method and display the current time. I can not for the life of me figure out how to pass the variable from the setCurrentTime method to the QTimer. Im sure it a really easy fix but I cant figure it out. Here is my code.
void noheatmode::setCurrentTime()
{
QTime time = QTime::currentTime();
QString sTime = time.toString("hh:mm:mm");
// ui->tempTimeNoHeatMode->append(sTime);
}
void noheatmode::on_timeButton_clicked()
{
QTimer *timer =new QTimer(this);
connect(timer,SIGNAL(timeout()), this, SLOT(setCurrentTime()));
timer->start(5000);
ui->tempTimeNoHeatMode->append(sTime);
}
If I got your problem right, you just have minutes variable instead of a seconds. Just change "hh:mm:mm" to "hh:mm:ss"
void noheatmode::setCurrentTime()
{
QTime time = QTime::currentTime();
QString sTime = time.toString("hh:mm:ss");
ui->tempTimeNoHeatMode->append(sTime);
}
With your code:
void noheatmode::on_timeButton_clicked()
{
QTimer *timer =new QTimer(this);
connect(timer,SIGNAL(timeout()), this, SLOT(setCurrentTime()));
timer->start(5000);
ui->tempTimeNoHeatMode->append(sTime);
}
This means that the function within SLOT will be called every 5000 milliseconds which = 5 seconds. What could be done then is that you set your function setCurrentTime() to update your text box every time it is called.
Example:
void Class::setCurrentTime()
{
QTime times = (QTime::currentTime());
QString currentTime=times.toString("hh:mm:ss");
ui->label->setText(currentTime);
//Assuming you are using a label to output text, else substitute for what you are using instead
//Every time this function is called, it will receive the current time
//and update label to display the time received
}

How to detect whether a player drops below a certain Y level

I'm trying to write bukkit plugin and one of the features I would like to add is when a player drops to Y level 8 or lower it runs a command on that player. My current case is I'd like to teleport them to other coordinates if they drop to Y level 8 or lower.
Is there a lite way of doing this without have a command run on every "move" event getting each players location then getting their y position then checking if it's less than 9? cause that seems like it would be a lot of work on the server if there are many people moving around.
There is a simpler way to do this without using runnables. Simply use a PlayerMoveEvent.
#EventHandler
public void onMove(PlayerMoveEvent event) {
if (event.getPlayer().getLocation().getY() <= 8) {
event.getPlayer().teleport(event.getPlayer().getLocation().add(0, 100, 0));
}
}
You could always use the SyncRepeatingTask. Then loop through all online players and check if their Y coordinate value is less than 9:
plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() {
public void run() {
for (Player player: Bukkit.getOnlinePlayers()) { // Loops through all online players
if (player.getLocation().getY() < 9) {
// The player's Y is below 9. Do whatever you want here. For example:
player.teleport(player.getLocation().add(0, 100, 0)); // Teleports the player 100 blocks above where they are
}
}
}
}, 50L, 50L); // Run every 2.5 seconds (50 ticks)
Just make sure to put the above code in your onEnable(), or a method that's called when your plugin is enabled.
If you want to change the repeat time dynamically based on the online players, you could use:
public void runTeleportTask() {
long time = Math.round(Bukkit.getServer().getOnlinePlayers().length / 10) + 10;
/*
Tweak the delay however you like, above we get the online players, and divide it by 10 then
add ten. We're adding 10 because you don't want to end up with a task that runs every
tick in this case... The fastest it would be needed would be every 10 ticks.
So if there were 100 players online, it would get 100/10 = 10, 10+10 = 20 = 1 second,
and if there were 250 players online, it would get 250/10 = 25, 25 + 10 = 35 = 1.75 seconds
*/
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
public void run() {
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.getLocation().getY() < 9) {
player.teleport(player.getLocation().add(0, 100, 0));
}
}
runTeleportTask();
}
}, time);
}
Then all you have to do is invoke runTeleportTask() in your onEnable() method.

Invoke a method only once from a class

I have a shape class in which there is a method (hitTest(int,int)) that continuously checks if the mouse is inside its bounds or not. In another method, I keep on checking if the mouse has stayed there for more than 1 sec.
If it has, trigger a function (by notification/event) that runs animation
It it has not, then don't trigger the animation
If it has already triggered the animation and the animation is running but the mouse leaves the area during this, trigger an interrupt function (by notification/event)
//OnLoad _initHover = false;
void update() //called continously in the application per frame
{
if(hitTest(getMouseX(), getMouseY())){
if(!_initHover){
_initHover = true;
_hoverStartTime = getCurrentTime(); //start hover time
cout<<"Start hist test\n";
}
//If it has hovered over the video for 1.0 sec
if((ofGetElapsedTimef() - _hoverStartTime) > 1.0){
cout<<"Hitting continously for 1 sec\n";
notificationCenter->postNotification(new AnimationStartNotification);
}
}
else{
_initHover = false;
notificationCenter->postNotification(new AnimationInterruptNotification);
}
}
The above code runs fine but there's a logical issue I am facing while trying to use. There are multiple instances of the above Shape class and each class consequently has their update() method as well. The mouse cursor has which has animationStarthandler and animationStophandlers is a single class in the whole application.
Issue 1: So, even when one of the shape just notifies the animationStarthandler to fire, the other shape classes on which hit test is false set the animation to interrupt and the animation does not run.
Issue 2: When the hit test succeeds and the cursor has been in the area for more than 1 sec, the hit test will keep on sending the notification to start the animation (anim's duration 1.5 sec approx.) How do I restrict the hit test to fire the animation only once and keep on firing the same animation again and again?
If in the main method of my application, I directly try to fire the animation by calling the method playAnimation in the pointer class, I get the required result. But I want to give this hover timing and animation functionality to the ShapeClass itself. Any suggestions?
I think that you should consider adding a new boolean, which holds the information of the triggering of the animation (called in the code sample _animationTriggered). This prevents shapes that have not triggered the animation to stop it and the animation that triggered it to make it several times.
if(hitTest(getMouseX(), getMouseY()))
{
if(!_initHover)
{
_initHover = true;
_hoverStartTime = getCurrentTime();
cout<<"Start hist test\n";
}
if((ofGetElapsedTimef() - _hoverStartTime) > 1.0)
{
if (!_animationTriggered)
{
cout<<"Hitting continously for 1 sec\n";
notificationCenter->postNotification(new AnimationStartNotification);
_animationTriggered = true;
}
}
}
else
{
if ( _animationTriggered )
{
_initHover = false;
notificationCenter->postNotification(new AnimationInterruptNotification);
_animationTriggered = false;
}
}
Don't forget to initialie this new boolean in the same place as _initHover