# Efficiency of code with 100+ objects

**URL:** <https://discourse.processing.org/t/efficiency-of-code-with-100-objects/33236>\
**Category:** Libraries\
**Created:** [November 1, 2021, 11:49pm UTC](https://discourse.processing.org/t/efficiency-of-code-with-100-objects/33236 "2021-11-01T23:49:55Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![tribblesontoast](https://avatars.discourse-cdn.com/v4/letter/t/e99b99/32.png) [@tribblesontoast](https://discourse.processing.org/u/tribblesontoast)\
**Post date:** [November 1, 2021, 11:49pm UTC](https://discourse.processing.org/t/efficiency-of-code-with-100-objects/33236/1 "2021-11-01T23:49:55Z")

</div>

Hi all,  
I’m writing a program that has numerous moving objects on screen and my frame rate starts to take a serious hit at around 100 - 110 objects. I’ve seen programs with thousands running easily so clearly I’m writing inefficient logic.

The program uses the twitter4j library to connect to the twitter API. I am searching for all of the words from a user supplied string. When one of these words is found an object is created and animates a wave across the screen and then reverses direction while a “drip” (don’t have a better name for it) grows from the end that hit the edge of the screen.  
When It reaches up over 100 objects(waves) on screen the frame rate starts to drop quite considerably, the lowest I’ve seen it is around 20fps which is disastrous for live performance.

I think the error is _either_ in the draw loop with the removal of the dead waves _or_ in the wave class itself as I’ve noticed that when there are a lot of “drips” is when the frame rate takes the biggest hit.

Am I on the right track for where the issue is? Is there a more efficient way to do what i’m trying to do? (Probably)

Here’s a video link of it in action showing the fluctuating frame rate:  
I do understand that with the frame rate it is a requested frame rate, but when it hits 30fps it is noticeably slower.

https://player.vimeo.com/video/641323744?h=bcb9cafaca&app_id=122963

This is as close as I can get to an MVCE, it does work, but I’ve had to take the API credentials out for security reasons.

Main Tab

```auto
import java.util.*;
import twitter4j.util.*;
import java.util.List;
import twitter4j.*;
import twitter4j.management.*;
import twitter4j.api.*;
import twitter4j.conf.*;
import twitter4j.json.*;
import twitter4j.auth.*;

TwitterStream twitterStream;

// quotes from: https://www.thoughtco.com/famous-short-quote-2833143
String uInput = "There are only two tragedies in life: one is not getting what one wants, and the other is getting it.";

ArrayList<String> words;
ArrayList<Wave> waves; 
int vertSpace;

int ySpacing;
char waveDirection;

boolean twitterInitialised = false;

void setup() {
  size(600, 1000);

  words = new ArrayList<String>();
  waves = new ArrayList<Wave>();

  userDefined = split(uInput.toLowerCase(), " ");
  vertSpace = userDefined.length;
  ySpacing = height/(vertSpace+1);
  openTwitterStream();
}

void draw() {
  background(12);

  if (int(random(10)) < 5) waveDirection = '-';
  else waveDirection = '+';

  for (int i = 0; i < words.size(); i++) {
    if (words.get(i) != null) {
      for (int n = 0; n < userDefined.length; n++) {
        if (words.get(i).equals(userDefined[n])) {
          waves.add(new Wave(ySpacing*(n+1), userDefined[n], waveDirection, int(random(200, 400))));
        }
      }
    }
  }

  for (Wave w : waves) w.draw();
  words.clear(); //clear the words array

  for (Iterator<Wave> w = waves.iterator(); w.hasNext(); ) {
    if (w.next().isDead()) {
      w.remove();
    }
  }

  fill(-1);
  textSize(12);
  text("Num Waves: " + waves.size(), 15, height-35);
  text("FPS: " + nf(int(frameRate), 0, 0), 125, height-35);
  noFill();
} 

void keyPressed() {
  if (key == ESC) {
    if (twitterInitialised) {
      twitterInitialised = false;
      twitterStream.cleanUp(); //closes down the stream
    }
    exit();
  }
}

```

Twitter Class

```auto
String[] userDefined;
//GetMentions men = new GetMentions();

// Stream it
void openTwitterStream() {

  // OAuth stuff
  ConfigurationBuilder cb = new ConfigurationBuilder();  
  ConfigurationBuilder b = new ConfigurationBuilder();  

  //stream api OAuth
  cb.setOAuthConsumerKey("---");
  cb.setOAuthConsumerSecret("---");
  cb.setOAuthAccessToken("---");
  cb.setOAuthAccessTokenSecret("---"); 

  b.setOAuthConsumerKey("---");
  b.setOAuthConsumerSecret("---");
  b.setOAuthAccessToken("---");
  b.setOAuthAccessTokenSecret("---");

  //the stream object
  twitterStream = new TwitterStreamFactory(cb.build()).getInstance();
 
  // filter is used to pass querys to the stream
  // see twitter4j's java doc
  FilterQuery filtered = new FilterQuery();

  // if you enter keywords here it will filter, otherwise it will sample
  String[] keywords = new String[userDefined.length];
  for (int i = 0; i < userDefined.length; i++) {
    keywords[i] = userDefined[i];
  }

  //track is like "search"... well kind of
  // for a better explanation go 
  // dev.twitter.com/streaming/overview/request-parameters"
  filtered.track(keywords);

  // the StatusListener interface is where the magic happens
  // code there will be executed upon tweets arriving
  // so we want to attach one to our stream
  twitterStream.addListener(listener);

  if (keywords.length == 0) {
    // sample() method internally creates a thread which manipulates TwitterStream 
    // and calls these adequate listener methods continuously.
    // ref //dev.twitter.com/streaming/reference/get/statuses/sample
    // "Returns a small random sample of all public statuses"
    twitterStream.sample();
  } else { 
    twitterStream.filter(filtered);
  }
  println("connected");
  twitterInitialised = true;
} 

// Implementing StatusListener interface
// the methods are pretty much self explantory
// they will be called according to the messages arrived
// onStatus is probably what you are lookikng for...
StatusListener listener = new StatusListener() {

  //@Override
  public void onStatus(Status status) {
    //println("@" + status.getUser().getScreenName() + " - " + status.getText());
    String s = status.getText().toLowerCase();
    String[] tweetWords = s.split(" ");
    for (int i = 0; i < tweetWords.length-1; i++) words.add(tweetWords[i]);
    //println("tweetWords: " + tweetWords.length);
    //tweetWords = null;
  }

  //@Override
  public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) {
    println("Got a status deletion notice id:" + statusDeletionNotice.getStatusId());
  }

  //@Override
  public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
    //println("Got track limitation notice:" + numberOfLimitedStatuses);
  }

  //@Override
  public void onScrubGeo(long userId, long upToStatusId) {
    println("Got scrub_geo event userId:" + userId + " upToStatusId:" + upToStatusId);
  }

  //@Override
  public void onStallWarning(StallWarning warning) {
    println("Got stall warning:" + warning);
  }

  //@Override
  public void onException(Exception ex) {
    ex.printStackTrace();
  }
};

```

Wave Class

```auto

class Wave {
  PVector startPos, endPos;
  String word;
  float strokeWidth;
  int yStored;
  float bezierRadius, bezierY;
  boolean randStartEndPos, dead;
  char waveDirection;
  float opacityIter, strokeIter;
  int dripAmt, opacity, growSpd, decaySpd;

  Wave(int yPos, String word, char waveDirection, int dripAmt) {
    yStored = yPos;
    this.word = word;
    bezierY = yPos;
    startPos = new PVector(-10, yPos);
    endPos = new PVector(-10, yPos);
    strokeWidth = 1;
    randStartEndPos = false;
    dead = false;
    this.waveDirection = waveDirection;
    this.dripAmt = height/dripAmt;
    opacity = 100;
    growSpd = width/(int(random(5, 250)));
    decaySpd = width/int(random(100, 150));
    opacityIter = 0.05;
    strokeIter = 0.25;
  }

  void draw() {

    stroke(-1, opacity);
    strokeWeight(strokeWidth);
    noFill();
    bezier(startPos.x, startPos.y, startPos.x+bezierRadius, bezierY, endPos.x-bezierRadius, bezierY, endPos.x, endPos.y);
    noStroke();
    fill(210, 55, 41);

    animate();

    if (randStartEndPos) {
      stroke(-1, opacity);
      strokeWeight(strokeWidth);
      line(0, startPos.y, startPos.x, startPos.y);
    }
  }

  void animate() {
    if (endPos.x < width+5 && !randStartEndPos) {
      endPos.x+=growSpd;
    } //
    else if (endPos.x > width && startPos.x > -11) {
      if (!randStartEndPos) {
        startPos.x = random(endPos.x-20, endPos.x-10);
        endPos.x = width+5;
        bezierRadius = (endPos.x-startPos.x)/2;
        randStartEndPos = true;
      }
      strokeWidth+=strokeIter;
      opacity-=opacityIter;
      startPos.x-=decaySpd;
      if (waveDirection == '+') bezierY += dripAmt;
      else bezierY -= dripAmt;
      bezierRadius = (endPos.x-startPos.x)/2;
    } 
    if (startPos.x < -10) dead = true;
  }

  boolean isDead() {
    return dead;
  }
}

```

---

<div class="post-metadata">

**Author:** ![hamoid](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/hamoid/32/58_2.png) [@hamoid](https://discourse.processing.org/u/hamoid)\
**Post date:** [November 2, 2021, 8:27am UTC](https://discourse.processing.org/t/efficiency-of-code-with-100-objects/33236/2 "2021-11-02T08:27:53Z")

</div>

Hi! Does it run any faster or slower with

```
size(600, 1000, P2D);

```

?

---

<div class="post-metadata">

**Author:** ![tribblesontoast](https://avatars.discourse-cdn.com/v4/letter/t/e99b99/32.png) [@tribblesontoast](https://discourse.processing.org/u/tribblesontoast)\
**Post date:** [November 4, 2021, 4:35am UTC](https://discourse.processing.org/t/efficiency-of-code-with-100-objects/33236/3 "2021-11-04T04:35:54Z")

</div>

Hi Abe,  
No that actually appeared to make it slower.

However, I have been playing and i have found that it is the following line in the Wave Class that appears to be causing the issue:

```auto
` bezier(startPos.x, startPos.y, startPos.x+bezierRadius, bezierY, endPos.x-bezierRadius, bezierY, endPos.x, endPos.y);`

```

when this is commented out the program happily will have up over 250 waves doing their thing without batting an eyelash. Though this does stop them from forming the “Drips” that I need.

Is there a better way to make a line bend as the video in the original post shows? Because my logic here is obviously not the correct approach.

---

<div class="post-metadata">

**Author:** ![hamoid](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/hamoid/32/58_2.png) [@hamoid](https://discourse.processing.org/u/hamoid)\
**Post date:** [November 4, 2021, 9:52am UTC](https://discourse.processing.org/t/efficiency-of-code-with-100-objects/33236/4 "2021-11-04T09:52:26Z")

</div>

I think it’s not just `bezier`, but the high `strokeWeight` (and transparency may add some slowness too). If `strokeWeight` is kept at 1 it does play smooth.

Maybe constructing a `TRIANGLE_STRIP` to draw the thick curve might be faster? In P2D? I’m not sure.

> **[Reference](https://processing.org/reference/createShape_.html)**
>
> The createShape() function is used to define a new shape. Once created, this shape can be drawn with the shape() function. The basic way to use the function defines new primitive shapes.…

(note that the reference shows `TRIANGLE_STRIP` in an example but it’s missing from the list of available parameters).

This is an example that could be used to build the triangle strip. It is an optimized version of [Reference / Processing.org](https://processing.org/reference/bezierTangent_.html) (removed `atan2`, `sin` and `cos`).

```auto
size(400, 400);
noFill();
bezier(340, 80, 40, 40, 360, 360, 60, 320);
stroke(255, 102, 0);
int steps = 16;
for (int i = 0; i <= steps; i++) {
  float t = i / float(steps);
  float x = bezierPoint(340, 40, 360, 60, t);
  float y = bezierPoint(80, 40, 360, 320, t);
  float tx = bezierTangent(340, 40, 360, 60, t);
  float ty = bezierTangent(80, 40, 360, 320, t);
  float d = dist(0, 0, tx, ty);
  line(x, y, x+32*ty/d, y-32*tx/d);
}

```

The idea is to not call `bezier()` at all but to build a mesh using `createShape` where the points are calculated like in this example and the value `32` is modified to specify the thickness of the curve.  
If the number of steps is high it can be slower, but maybe worth trying…

---

<div class="post-metadata">

**Author:** ![paulgoux](https://avatars.discourse-cdn.com/v4/letter/p/b9bd4f/32.png) [@paulgoux](https://discourse.processing.org/u/paulgoux)\
**Post date:** [November 4, 2021, 1:44pm UTC](https://discourse.processing.org/t/efficiency-of-code-with-100-objects/33236/5 "2021-11-04T13:44:11Z")

</div>

I’ve noticed this problem before in the past, changing the stroke weight past 1 in my software caused significant slow downs. I wasn’t sure if I had made an error somehow or if it was something else. Note it gets worse the bigger the stroke size.

---

<div class="post-metadata">

**Author:** ![tribblesontoast](https://avatars.discourse-cdn.com/v4/letter/t/e99b99/32.png) [@tribblesontoast](https://discourse.processing.org/u/tribblesontoast)\
**Post date:** [November 8, 2021, 3:28am UTC](https://discourse.processing.org/t/efficiency-of-code-with-100-objects/33236/6 "2021-11-08T03:28:02Z")

</div>

I see what both of you mean, the changing stroke is a real kick in the teeth. With a stroke of 1 I can get up over 300 objects before it complains. As a way around this I think I’ll just set each object to have a random stroke weight when it is created and take out the increase of size.

I had considered doing it like somewhat like this in the first place, but I was trying to minimise the number of segments. Perhaps I should have trusted my gut. I’ll give it a whack and see how it goes.

i’ll keep you posted.

cheers.
