I suggest you use lazy evaluation. Skip to 3) to learn more.
1) Don't optimize too early
First we need to define what you mean by "resource intensive". Which resource? CPU? Disk space? I/O? What's the bottleneck?
The usual approach is to build something first, profile it or stress test it until it begins to fail, and then look at which resources got maxed out. If your program reaches 100% CPU usage before it reaches 100% memory usage, then the CPU is the bottleneck - in other words, you are CPU-constrained.
Now, for the game you're describing, I assume the current number of resources for each player will be stored in a database. If so, then database I/O will most likely be your bottleneck, because each run of the updating a row on a database is relatively expensive compared to, say, updating a value in memory.
Updating those cronjobs might also be expensive depending on where they are stored. Crontabs are usually stored on the disk (and disk I/O is expensive) but sometimes they are stored in memory instead. It depends on whether you want persistence.
That being said, "expensive" is relative. Modern servers are fast. Your approach might scale better than you think. If you need to scale it even further, it might be cheaper to pay for extra hardware than to invest the man-hours to optimize it.
2) Suggested improvements
Updating a cronjob and the database on each run is unnecessarily complex. For starters, let's try updating only one thing at a time. Here's your database schema:
resourceAmount INT | productionCooldown INT | lastResourceDrop DATETIME
- Run a cronjob every second (or every 500 milliseconds... set this to whatever you want).
- On every run, the job should check every row in the resources table. For every resource:
- determine how much time has passed since the last update (
elapsedTime = currentTime - lastResourceDrop)
- determine how many resources the player got during that time:
resourcesToAdd = floor(productionCooldown / elapsedTime)
- add the amount of time it took for them to get those resources:
lastResourceDrop = lastResourceDrop + productionCooldown * resourcesToAdd
This is less complex, and therefore easier to troubleshoot, compared to the approach with multiple cronjobs. However, now we're iterating over every row of the database every single second, which is suboptimal. Here's how we can improve this further:
getPlayerResource method or API and use it whenever anything in your game wants to know the current amount of resources a player has. This method should:
- Update the player's resources by following the steps above.
- Return the player's current resource amount.
- No cronjobs
- It only updates resources for active players. Inactive players won't slow down your algorithm.
- Thread safe. If you perform all the database operations as a transaction, you won't run into corner cases where you update a player's resources twice in a row.
- Slightly slower than just reading a player's current resources without performing any checks or updates.