Pirate Wave Defense Core Script

I don’t understand how each wave will increment if it’s the same code?

if CurrentWave == 1 then
      -- your code
elseif CurrentWave <= 4 then
  print(CurrentWave.."0 enemies")        
end

You are never incrementing the CurrentWave value? The code I suggested takes the value of the variable CurrentWave and concatenates it to "0 enemies".

1 Like
if #npcs == 0 then
	CurrentWave += 1
	Wave(player)
end

The CurrentWave increments after every wave once all NPC’s are killed.
Then the CurrentWave global variable is updated to the next wave, then the Wave() function is called.

local function Wave(player)
	player.PlayerGui.CoreUI.WaveLabel.Text = "Wave "..tostring(CurrentWave)
	for i, v in pairs(game.Workspace.Wave:GetChildren()) do
		v:Destroy()
	end
	if CurrentWave == 1 then
		local waveCopy = game.ReplicatedStorage.Wave1:Clone()
		waveCopy.Parent = game.Workspace.Wave
	elseif CurrentWave == 2 then
		print('20 enemies')
	elseif CurrentWave == 3 then
		print('30 enemies')
	elseif CurrentWave == 4 then
		print('40 enemies')
	end
end

I was just using print to test before I do.

I don’t quite get what you are trying to ask. The code i just suggested should only replace

with

if CurrentWave == 1 then
    local waveCopy = game.ReplicatedStorage.Wave1:Clone()
    waveCopy.Parent = game.Workspace.Wave
elseif CurrentWave <= 4 then
   print(CurrentWave.."0 enemies")
end

It is not relating to the incrementing of CurrentWave. One other small this is that CurrentWave is not a global, it is a local. Globals are initialized like so : Var = Value. The major difference is how they are stored in memory; as I highlighted in the main post above.

1 Like

Yes, so how will I tell the script that Wave 3 has more enemies than Wave 2?

onedar gave you that code, since the code that you gave him to review, contained print statements to print the amount of enemies in that wave, how ever the number of enemies increments 10 every round so if it is the round 2, it will have 20 enemies, so thats why onedar suggested you that piece of code, since that, with that code it will just concanate the number of wave with 0, again if the wave is 2 it will be 20, so thats why onedar suggested the code to shorten the prints, now if you were just doing that for testing purposes you should have been put a comment or something like that, since that, when you put a code in Code Review, the people that reviews it, will review the current code, not knowing if you will change it, so they will take it very literally, if your goal is to make something different every wave just keep it like that, how ever if you will do some sort of pattern just like those prints, try to implement the code that onedar gave to you.

4 Likes

One major problem with your script is the misuse of pairs, pairs should only be used on dictionaries. Use of it on arrays is discouraged. Pairs iterates over a table without sequence.

What? You can use any of those 2, using pairs on arrays is not discouraged. You need to know the difference between Ipairs and pairs so you know when to use them. An use case of pairs on arrays would be to loop through them indefinitely and pick a random value.

  1. Right here, you are declaring a Global. Globals are stored in the Heap. Accessing the globals are quite slower than the traditional stack for locals . You can simply just do : local CurrentValue = 1

Stacks only are negligibly faster than the global environment.

function pairs(Table)
return next, Table, nil
end

That isn’t the actual source code of pairs, it’s the iterator function returned by pairs.

I also see you are commonly using table.insert() and table.remove . These are unneeded.

This is preferable and is not “unneeded”. It’s like you saying .Magnitude is unneeded when you can just do sqrt(x^2 + y^2 + z^2).

1 Like

At the point when an individual uses ipairs for a “for loop”, the reader consequently realizes that the table given is an array. With pairs, this is not manifested. The performance boost is there, as seen in my benchmark. The results were astonishing, they were greater than I expected :
13:02:45.174 Pairs : 3114, Ipairs : 996886. - Server - Main:25
It was reliably quicker. There is basically no explanation, other than the explanation you brought up, to utilize pairs on an array.

As I highlighted in my post above, I misread his code and he did initialize it as a local. Your assertion is deluding. Memory in the Heap is not as tightly managed as in the stack. The stack gets gced after the scope ends. On the other hand, variables located in the heap are not as tightly managed as variables in the stack. They still get gced, don’t get me wrong, but not as quickly. I can’t help to contradicting this assertion as I don’t see it being valid, if you could provided some proof/articles backing your position that would be nice.

That is my fault, I worded it wrong.

table.insert() for appending is unneeded, it is calling a function just to achieve the equivalent as table[#table+1] = value. The analogy you made is definitely not an incredible one, it holds some similitude, but, .Magnitude is very cleanly achieving the same as the distance formula, in this case I would use .Magnitude instead of writing a whole formula down. Also, .Magnitude isn’t calling an extraneous function, it is indexing a property. For “inserting” values into tables, I fully agree with use of table.insert(), but for appending I don’t.

Have a nice day :slightly_smiling_face:,
onedar

2 Likes

The GC assertion isn’t really accurate. As far as Lua’s GC is concerned, a GC-able object is the same whether it has references from the stack or elsewhere. This wouldn’t matter anyways as it doesn’t have a real impact.

The reason local is used is more about consistency and scope management than it is about miniscule performance boosts.

As for the last example, both have a built in function already provided for you to use. I don’t see the difference. Roblox could also at any point optimize table.insert() to be faster than the indexing method, too.

2 Likes

I don’t see anything good about declaring a variable as a global. Why recommend declaring variables as global? When accessing memory is slower in the heap, there is no guaranteed efficient use of space, memory may become fragmented over time as blocks of memory are allocated, then freed, and memory in the heap isn’t memory managed as tightly. Memory leaks can also occur. On the other hand, accessing memory in the stack is faster and the space is managed by the cpu tightly, allowing memory to not be fragmented in the stack. When writing efficient and readable code, you should always declare variables as locals, there is no reason not to. In the case you do need a variable accessible by all of the script you can simply do.

local Var

local function foo(Val)
    Var = Val
end

Source1
Source2
Source3

3 Likes

Re read my post. At no point did I recommend using globals. I corrected the reason as to why locals should be used. The performance impact is negligible in most cases.

Also, you seem to be confused. The Lua stack isn’t special or “managed by the cpu”. In fact, the Lua stack is just a normal heap allocated array. You aren’t seeing faster look up because of it being closer together, you’re seeing it because Lua globals are stored in a dictionary while stack values are indexed out of an array.

3 Likes

13:02:45.174 Pairs : 3114, Ipairs : 996886. - Server - Main:25
It was reliably quicker. There is basically no explanation, other than the explanation you brought up, to utilize pairs on an array.

Just because of a negligible boost over pairs doesn’t indicate you shouldn’t use pairs.

At the point when an individual uses ipairs for a “for loop”, the reader consequently realizes that the table given is an array. With pairs, this is not manifested.

This still doesn’t prove your point here. Just because you are looping through ipairs doesn’t mean the reader will realize you’re looping through an array.

As I highlighted in my post above, I misread his code and he did initialize it as a local. Your assertion is deluding. Memory in the Heap is not as tightly managed as in the stack. The stack gets gced after the scope ends. On the other hand, variables located in the heap are not as tightly managed as variables in the stack. They still get gced, don’t get me wrong, but not as quickly. I can’t help to contradicting this assertion as I don’t see it being valid, if you could provided some proof/articles backing your position that would be nice.

Your point here is also not sufficient. It is perfectly fine to use globals, the performance difference between stacks and globals in luau is negligible but yes, you should still use local but also should use globals when needed.

It significantly does. Onedar ran some tests and pairs was 0.1167416118551 seconds slower than ipairs for 100 elements amortized. Benchmark: Benchmark
You say that’s negligible but what happens if you wanted to misuse pairs 6 more times? You will end up seeing much more substantial performance drops in your code. Now you might say what if I only use pairs in some places and ipairs in some places? That only just brings inconsistency and miss-context to your code.

Just as perfectly Onedar explained, by the use of a correct iterator on a corresponding set will show the reader what your iterating through. Generally most normal people use pairs on sets containing non-numerical indices and people use ipairs on sets that do contain numeric indices. Such consistent pattern will bring correct context to your code.

The performance difference is pretty big. There is a huge performance drop when you define a global. This is because the stack is linear data structure while the heap is more complexly structured, its much more slower to access compared to a linear data structure. There is never a real reason to use globals. Putting performance aside, memory handling on the heap is very inefficient. Stack memory will never become fragmented whereas Heap memory can become fragmented as blocks of memory are first allocated and then freed. Putting efficiency aside, with the use of globals, your clogging up your main scope. You will run out of resources quickly.
My benchmarks:
Benchmark

3 Likes

This is misleading and incorrect information.
The Stack is “special” and is managed by the CPU. The Stack is a linearly structured data structure while a Heap has a more hierarchical structure. The Stack does provide much faster access time than a Heap. The main reason for why the Heap has worse access time than a Stack is because Stack memory is allocated in a contiguous block whereas Heap memory is allocated in any random order. Not only that but access time is also justified by how the Heap is structured. The main problem Heap memory has is memory fragmentation and in some static languages you will generally have to de-allocate memory your self.

3 Likes

Where are you getting this from? You realize the Lua stack is just a heap allocated C array? This is easily verifiable information.

https://www.lua.org/source/5.1/lstate.h.html#lua_State

Notice how the stack is stored as a StkId which is a TValue*; a pointer to a C array.

Moreover, the difference between accesses would be so negligible that it is in fact the dictionary lookup delay that you’re observing between globals and locals. Globals are stored in a dictionary.

5 Likes

I’m not sure how to address this. All of these points so far have been outlandish and/or wrong.

This is the Lua source code. This is what runs on your computer when you run Lua. Lua isn’t self hosted, it’s a language made in C. Roblox’s implementation is a fork of the Lua 5.1 C implementation.

The compiled (hereafter “native”) stack is irrelevant; the native stack is constantly changing whenever Lua calls into Roblox code or Roblox code into Lua or native functions entering and exiting.

What does “the Lua stack gets transcompiled into C” mean? Lua’s stack is an array of values in on the heap. The lua_State struct in the source code has a stack field which refers to it, and 2 fields (top, base) to help identify which parts of it are active. When Lua code is compiled, it compiles to a bytecode format which runs in the Lua interpreter.

The bytecode comes with the variable offsets baked into the instructions. Instead of “access variable abc”, you see “access variable at index 4” for example.

The reason you see local variables having faster lookup times is because internally it’s an array index. It just indexes from the Lua stack, which is on the heap. Alternatively, if abc was a global, a GETGLOBAL instruction would be produced, which does a dictionary look-up over the environment table that getfenv(1) normally returns.

I’m not sure what this refers to. The Lua stack is stored on the heap. The heap is just available and allocated RAM pages.

His sources are for the native stack, which like I mentioned before has nothing to do with this.

In Lua, because, as I explained, declaring a global involves a dictionary which is orders of magnitude slower to access than an array. If you test this on any compiled language (C, C++, Rust, etc) you will find that heap allocated values in those languages are at worst ever so slightly slower than stack values.

It is a rounding error usually, and only shows itself when you are dealing with gigantic amounts of data. This is because of the CPU cache and other optimizations in the hardware. These optimizations generally don’t affect Lua, because Lua is a language hosted on top of C. It is not running directly on the CPU.

5 Likes

It’s the what now?

You shouldn’t have to worry about the binary generated regardless of compiler, which is what I assume you’re trying to say by “how C compiles”.

The Lua stack doesn’t get “transcompiled” into C. It is as @Autterfly says, the Lua stack is merely a heap-allocated array. C isn’t “transcompiled”, much less at runtime.

1 Like

It significantly does. Onedar ran some tests and pairs was 0.1167416118551 seconds slower than ipairs for 100 elements amortized. Benchmark: Benchmark
You say that’s negligible but what happens if you wanted to misuse pairs 6 more times? You will end up seeing much more substantial performance drops in your code. Now you might say what if I only use pairs in some places and ipairs in some places? That only just brings inconsistency and miss-context to your code.

What? The difference is negligible. When I did a bench mark with about 500 elements in a table, pairs was only slower by 0.04 seconds. You’re also wrong about the “mis-use of pairs”, you should use Ipairs when you want to loop definitely and stop at a nil value and pairs when you want to loop indefinitely and not stop at a nil value.

Not to mention the fact I’ve seen a lot of uses cases where you want to loop through an dictionary definitely.

Generally most normal people use pairs on sets containing non-numerical indices and people use ipairs on sets that do contain numeric indices.

Ipairs was never made to be used only on arrays, it was made so you can loop through tables in order and not indefinitely. Just because other people use it and you don’t doesn’t mean there will be inconsistency in code.

“Not to mention the fact I’ve seen a lot of uses cases where you want to loop through an dictionary definitely.”

The performance difference is pretty big. There is a huge performance drop when you define a global.

This is just incorrect information you’ve given here. It is necessary to backup your post with some proof. The performance difference in no way is big, it is negligible and you should use globals when they are needed, they exist for a reason. The rest is explained by Autterfly so I won’t bother anymore to explain my point here.

** [Small Edit] **

As Autterfly said, global variables are added to an dictionary which is known as the global environment (slightly slower indexing than arrays) and correct me if I’m wrong; whenever you declare a global variable, an instruction SETGLOBAL is invoked. These are what makes global variables negligibly slower than local variables.

As tested from your code, only about 0.0003 seconds of difference, I don’t know how this is a big performance cost. :confused:
image

This better come with a really good citation. ipairs is made for arrays specifically, it’s just how it’s designed, and it can run indefinitely.

There’s really no reason to use pairs over ipairs for arrays, especially when the latter conveys much better what its parameter is.

5 Likes

If you want to loop through a dictionary to begin with you use pairs. If you want to loop through an array you use ipairs, because it doesn’t work on non-numeric non-ordered keys. Assuming what you meant was looping through an array in a random order, you would use neither. pairs doesn’t guarantee any kind of ordering, not even random. You’d want to use some combination of math.random and table.remove at this point.

4 Likes