Skip to content

Commit cd8ad32

Browse files
committed
Add liveatc and fix webaudio counter not decreasing w/ client errors
Fixes #52 Resolved an error where a client having an error sending the error to the server would not decrease the webaudio counter, leaving players without any slots left to use webaudio without rejoining.
1 parent 2247280 commit cd8ad32

File tree

6 files changed

+106
-113
lines changed

6 files changed

+106
-113
lines changed

WHITELIST.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ This is the default whitelist that webaudio will abide by unless a ``webaudio_wh
2929
| Onedrive || onedrive.live.com | Onedrive content | 🚧 |
3030
| Mattjeanes ytdl || youtubedl.mattjeanes.com | Ytdl host by mattjeanes https://github.com/MattJeanes/YouTubeDL | 🚧 |
3131
| MyInstants | ✔️ | myinstants.com | Sound effects | https://myinstants.com/media/sounds/taco-bell-bong-sfx.mp3 |
32-
| Moonbase Alpha TTS | ✔️ | tts.cyzon.us | TTS almost identical to moonbase alpha's | https://tts.cyzon.us/tts?text=bruh |
32+
| Moonbase Alpha TTS | ✔️ | tts.cyzon.us | TTS almost identical to moonbase alpha's | https://tts.cyzon.us/tts?text=bruh |
33+
| LiveATC || liveatc.net | US Air Traffic Control radio host | https://www.liveatc.net/hlisten.php?mount=lszh1_app_east&icao=lszh |

lua/autorun/stopwatch.lua

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
2-
31
--[[
42
StopWatch/TimerEx Library
53
Author: Vurv
@@ -31,7 +29,10 @@ local timer_now = RealTime
3129
---@param duration number # How long the stopwatch will last
3230
---@param callback fun(self: Stopwatch) # What to run when the stopwatch finishes.
3331
---@return Stopwatch
34-
local function Initialize(_, duration, callback)
32+
function Stopwatch.new(duration, callback)
33+
assert(duration, "bad argument #1 to 'Stopwatch.new' (number expected)")
34+
assert(callback, "bad argument #2 to 'Stopwatch.new' (function expected)")
35+
3536
local self = setmetatable({}, Stopwatch)
3637
self.playback_rate = 1
3738
self.playback_now = timer_now()
@@ -64,8 +65,11 @@ local function Initialize(_, duration, callback)
6465
return self
6566
end
6667

68+
-- Backwards compatibility with __call version
6769
setmetatable(Stopwatch, {
68-
__call = Initialize
70+
__call = function(self, ...)
71+
return self.new(...)
72+
end
6973
})
7074

7175
--- Pauses a stopwatch at the current time to be resumed with :Play
@@ -196,5 +200,6 @@ function Stopwatch:GetLooping()
196200
end
197201

198202
_G.StopWatch = Stopwatch
203+
_G.Stopwatch = Stopwatch
199204

200205
return Stopwatch

lua/autorun/webaudio.lua

Lines changed: 59 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
(part 1)
44
]]
55

6-
--- x86 branch is on an outdated luajit that doesn't support binary literals. Woo
7-
-- i love the game "garry's mod" created by Garry Newman as a mod for Valve's Source game engine and released in December 2004, before being expanded into a standalone release that was published by Valve in November 2006.
6+
--- x86 branch is on LuaJIT 2.0.4 which doesn't support binary literals
87
local function b(s)
98
return tonumber(s, 2)
109
end
@@ -133,6 +132,7 @@ end
133132
---@field id integer # Custom ID for webaudio stream allocated between 0-MAX
134133
---@field ignored GCRecipientFilter # Players to ignore when sending net messages.
135134
---@field mode WebAudioMode
135+
---@field hook_destroy table<fun(self: WebAudio), boolean> # SHARED. Hooks to run before/when the stream is destroyed.
136136
_G.WebAudio = {}
137137
WebAudio.__index = WebAudio
138138

@@ -203,12 +203,22 @@ function WebAudio:IsDestroyed()
203203
return self.destroyed
204204
end
205205

206+
--- Calls the given function right before webaudio destruction.
207+
---@param fun fun(self: WebAudio)
208+
function WebAudio:OnDestroy(fun)
209+
self.hook_destroy[fun] = true
210+
end
211+
206212
--- Destroys a webaudio object and makes it the same (value wise) as WebAudio.getNULL()
207213
--- @param transmit boolean? If SERVER, should we transmit the destruction to the client? Default true
208214
function WebAudio:Destroy(transmit)
209215
if self:IsDestroyed() then return end
210216
if transmit == nil then transmit = true end
211217

218+
for callback in pairs(self.hook_destroy) do
219+
callback(self)
220+
end
221+
212222
if CLIENT and self.bass then
213223
self.bass:Stop()
214224
elseif transmit then
@@ -394,30 +404,9 @@ end
394404

395405
--- Used internally. Should be called both server and client as it doesn't send any net messages to destroy the ids to the client.
396406
-- Called on WebAudio reload to stop all streams
397-
if CLIENT then
398-
function WebAudioStatic.disassemble()
399-
for _, stream in WebAudio.getIterator() do
400-
stream:Destroy(false)
401-
end
402-
end
403-
else
404-
function WebAudioStatic.disassemble()
405-
-- Todo: This is a dumb hack. There should be a hook mechanism so that the E2 core doesn't need special treatment in webaudio.
406-
local E2StreamCounter = WebAudio.E2StreamCounter
407-
408-
if E2StreamCounter then
409-
for _, stream in WebAudio.getIterator() do
410-
if stream.owner and E2StreamCounter[stream.owner] then
411-
E2StreamCounter[stream.owner] = math.max( E2StreamCounter[stream.owner] - 1, 0 )
412-
end
413-
414-
stream:Destroy(false)
415-
end
416-
else
417-
for _, stream in WebAudio.getIterator() do
418-
stream:Destroy(false)
419-
end
420-
end
407+
function WebAudioStatic.disassemble()
408+
for _, stream in WebAudio.getIterator() do
409+
stream:Destroy(false)
421410
end
422411
end
423412

@@ -426,51 +415,50 @@ local function createWebAudio(_, url, owner, bassobj, id)
426415
-- assert( owner and isentity(owner) and owner:IsPlayer(), "'owner' argument must be a valid player." )
427416
-- Commenting this out in case someone wants a webaudio object to be owned by the world or something.
428417

429-
local self = setmetatable({}, WebAudio)
430-
431-
self.id = id or allocID()
432-
-- allocID will return nil if there's no slots left.
433-
if id == nil and not self.id then
434-
error("Reached maximum amount of concurrent WebAudio streams!")
435-
end
418+
local self = setmetatable({
419+
-- allocID will return nil if there's no slots left.
420+
id = assert( id or allocID(), "Reached maximum amount of concurrent WebAudio streams!" ),
421+
url = url,
422+
owner = owner,
423+
mode = WebAudio.MODE_3D,
436424

437-
self.url = url
438-
self.owner = owner
439-
self.mode = WebAudio.MODE_3D
425+
--#region mutable
426+
playing = false,
427+
destroyed = false,
440428

441-
-- Mutable --
442-
self.playing = false
443-
self.destroyed = false
429+
parented = false,
430+
parent = nil, -- Entity
444431

445-
self.parented = false
446-
self.parent = nil -- Entity
432+
modified = 0,
433+
playback_rate = 1,
434+
volume = 1,
435+
time = 0,
447436

448-
self.modified = 0
437+
radius = math.min(200, WAMaxRadius:GetInt()), -- Default IGmodAudioChannel radius
438+
radius_sqr = math.min(200, WAMaxRadius:GetInt()) ^ 2,
449439

450-
self.playback_rate = 1
451-
self.volume = 1
452-
self.time = 0
440+
pos = nil,
441+
direction = Vector(0, 0, 0),
442+
looping = false,
443+
--#endregion mutable
453444

454-
self.radius = math.min(200, WAMaxRadius:GetInt()) -- Default IGmodAudioChannel radius
455-
self.radius_sqr = self.radius * self.radius
445+
--#region net vars
446+
needs_info = SERVER, -- Whether this stream still needs information from the client.
447+
length = -1,
448+
filename = "",
449+
fft = {},
456450

457-
self.pos = nil
458-
self.direction = Vector(0, 0, 0)
459-
self.looping = false
460-
-- Mutable --
451+
hook_destroy = {}
452+
--#endregion net vars
453+
}, WebAudio)
461454

462-
-- Net vars
463-
self.needs_info = SERVER -- Whether this stream still needs information from the client.
464-
self.length = -1
465-
self.filename = ""
466-
self.fft = {}
467455

468456
if CLIENT then
469457
self.bass = bassobj
470458
self.parent_pos = Vector(0, 0, 0) -- Parent pos being nil means we will go directly to the parent's position w/o calculating local pos.
471459
else
472460
-- Stream will be set to 100 second length until the length of the audio stream is determined by the client.
473-
self.stopwatch = StopWatch(100, function(watch)
461+
self.stopwatch = StopWatch.new(100, function(watch)
474462
if not watch:GetLooping() then
475463
self:Pause()
476464
end
@@ -495,6 +483,16 @@ setmetatable(WebAudio, {
495483
__call = createWebAudio
496484
})
497485

486+
--- Creates a new webaudio object. Prefer this over the __call version
487+
---@param url string
488+
---@param owner GPlayer?
489+
---@param bassobj GIGModAudioChannel?
490+
---@param id number?
491+
---@return WebAudio
492+
function WebAudio.new(url, owner, bassobj, id)
493+
return createWebAudio(WebAudio, url, owner, bassobj, id)
494+
end
495+
498496
--[[
499497
Whitelist handling
500498
]]
@@ -569,7 +567,10 @@ local Whitelist = {
569567
simple [[myinstants.com]],
570568

571569
-- TTS like moonbase alpha's
572-
simple [[tts.cyzon.us]]
570+
simple [[tts.cyzon.us]],
571+
572+
-- US Air Traffic Control radio host
573+
simple [[liveatc.net]]
573574
}
574575

575576
local OriginalWhitelist = table.Copy(Whitelist)

lua/entities/gmod_wire_expression2/core/custom/webaudio.lua

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ local Enabled, AdminOnly, FFTEnabled = Common.WAEnabled, Common.WAAdminOnly, Com
2020
local MaxStreams, MaxVolume, MaxRadius = Common.WAMaxStreamsPerUser, Common.WAMaxVolume, Common.WAMaxRadius
2121

2222
local StreamCounter = WireLib.RegisterPlayerTable()
23-
WebAudio.E2StreamCounter = StreamCounter
2423

2524
local CREATE_REGEN = 0.3 -- 300ms to regenerate a stream
2625
local NET_REGEN = 0.1 -- 100ms to regenerate net messages
@@ -57,8 +56,16 @@ registerType("webaudio", "xwa", WebAudio.getNULL(),
5756
)
5857

5958
local function registerStream(self, url, owner)
60-
local stream = WebAudio(url, owner)
61-
table.insert(self.data.webaudio_streams, stream)
59+
local stream = WebAudio.new(url, owner)
60+
61+
local idx = table.insert(self.data.webaudio_streams, stream)
62+
StreamCounter[owner] = (StreamCounter[owner] or 0) + 1
63+
64+
stream:OnDestroy(function(_)
65+
self.data.webaudio_streams[idx] = nil
66+
StreamCounter[owner] = StreamCounter[owner] - 1
67+
end)
68+
6269
return stream
6370
end
6471

@@ -149,18 +156,25 @@ end
149156

150157
--- Generic Burst limit until wiremod gets its own like Starfall.
151158
-- Also acts as a limit on the number of something.
152-
local Burst = setmetatable({}, {
153-
__call = function(self, max, regen_time)
154-
return setmetatable({
155-
max = max, -- Will start full.
156-
157-
regen = regen_time,
158-
tracker = WireLib.RegisterPlayerTable()
159-
}, self)
160-
end
161-
})
159+
---@class Burst
160+
---@field max number
161+
---@field regen number
162+
---@field tracker table<GPlayer, { stock: number, last: number }>
163+
local Burst = {}
162164
Burst.__index = Burst
163165

166+
---@param max number
167+
---@param regen_time number
168+
---@return Burst
169+
function Burst.new(max, regen_time)
170+
return setmetatable({
171+
max = max, -- Will start full.
172+
173+
regen = regen_time,
174+
tracker = WireLib.RegisterPlayerTable()
175+
}, Burst)
176+
end
177+
164178
function Burst:get(ply)
165179
local data = self.tracker[ply]
166180
if data then
@@ -188,6 +202,7 @@ function Burst:check(ply)
188202
end
189203
end
190204

205+
---@param ply GPlayer
191206
function Burst:use(ply)
192207
local data = self.tracker[ply]
193208
if data then
@@ -212,21 +227,18 @@ function Burst:use(ply)
212227
end
213228
end
214229

215-
local CreationBurst = Burst( MaxStreams:GetInt(), CREATE_REGEN ) -- Prevent too many creations at once. (To avoid Creation->Destruction net spam.)
216-
local NetBurst = Burst( 10, NET_REGEN ) -- In order to prevent too many updates at once.
230+
local CreationBurst = Burst.new( MaxStreams:GetInt(), CREATE_REGEN ) -- Prevent too many creations at once. (To avoid Creation->Destruction net spam.)
231+
local NetBurst = Burst.new( 10, NET_REGEN ) -- In order to prevent too many updates at once.
217232

218233
cvars.RemoveChangeCallback("wa_stream_max", "wa_stream_max")
219234

220235
cvars.AddChangeCallback("wa_stream_max", function(_cvar_name, _old, new)
221236
CreationBurst.max = new
222237
end, "wa_stream_max")
223238

224-
local function checkCounter(ply, use)
239+
local function checkCounter(ply)
225240
local count = StreamCounter[ply] or 0
226241
if count < MaxStreams:GetInt() then
227-
if use then
228-
StreamCounter[ply] = count + 1
229-
end
230242
return true
231243
end
232244
return false
@@ -257,7 +269,7 @@ registerFunction("webAudio", "s", "xwa", function(self, args)
257269
end
258270

259271
-- Stream Count Quota
260-
if not checkCounter(ply, true) then
272+
if not checkCounter(ply) then
261273
E2Lib.raiseException("Reached maximum amount of WebAudio streams! Check webAudioCanCreate or webAudiosLeft before calling!", nil, self.trace)
262274
end
263275

@@ -367,15 +379,8 @@ registerFunction("destroy", "xwa:", "n", function(self, args)
367379
local op1 = args[2]
368380
---@type WebAudio
369381
local this = op1[1](self, op1)
370-
371382
-- No limit here because they'd already have been limited by the creation burst.
372-
local owner = this.owner or self.player
373-
-- Prevent people destroying others streams and getting more slots? Would be weird.
374-
if this:Destroy() then
375-
StreamCounter[owner] = StreamCounter[owner] - 1
376-
return 1
377-
end
378-
return 0
383+
return this:Destroy() and 1 or 0
379384
end)
380385

381386
-- void webaudio:update()
@@ -849,21 +854,15 @@ registerCallback("construct", function(self)
849854
end)
850855

851856
registerCallback("destruct", function(self)
852-
---@type GPlayer
853-
local owner = self.player
854857
---@type table<number, WebAudio>
855858
local streams = self.data.webaudio_streams
856859

857-
local count = StreamCounter[owner] or 0
858-
859860
for k, stream in pairs(streams) do
860861
if stream:IsValid() then
861-
count = count - 1 -- Assume StreamCounter[owner] is not nil since we're looping through self.webaudio_streams. If it is nil, something really fucked up.
862862
stream:Destroy(true)
863863
end
864864
streams[k] = nil
865865
end
866-
StreamCounter[owner] = count
867866
end)
868867

869868
--#endregion cleanup

0 commit comments

Comments
 (0)