Skip to content

Instantly share code, notes, and snippets.

@Quenty
Last active November 18, 2022 15:59
Show Gist options
  • Save Quenty/803e0a6f36804e327c60347e52803359 to your computer and use it in GitHub Desktop.
Save Quenty/803e0a6f36804e327c60347e52803359 to your computer and use it in GitHub Desktop.
---
-- @classmod CameraChallenge
-- @author Quenty
local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))
-- Challenge:
-- 1. Make `camera' always point to the the current or last known camera.
-- a. Problem: CurrentCamera can be nil.
-- b. Guarantee that `camera' is never nil.
-- 2. Make `Goodviewport' always be the current actual size of the Goodviewport.
-- a. Problem: ViewportSize can falsely report (1,1) before it gets fixed up by RenderPrepare.
-- b. Guarantee that `Goodviewport' is never (1,1).
-- 3. Yield until the above guarantees are met.
-- 4. Don't leak memory.
-- See: https://gist.github.com/Fraktality/31bc9cb5abf2af4340cd3a6a775c616d
local Workspace = game:GetService("Workspace")
local BaseObject = require("BaseObject")
local Maid = require("Maid")
local Promise = require("Promise")
local ValueObject = require("ValueObject")
local CameraChallenge = setmetatable({}, BaseObject)
CameraChallenge.ClassName = "CameraChallenge"
CameraChallenge.__index = CameraChallenge
function CameraChallenge.new()
local self = setmetatable(BaseObject.new(), CameraChallenge)
self.LastCamera = ValueObject.new()
self._maid:GiveTask(self.LastCamera)
self.GoodViewport = ValueObject.new()
self._maid:GiveTask(self.GoodViewport)
self._maid:GiveTask(Workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
self.LastCamera.Value = Workspace.CurrentCamera
end))
self._maid:GiveTask(self.LastCamera.Changed:Connect(function(...)
self:_handleCameraChanged(...)
end))
self.LastCamera.Value = Workspace.CurrentCamera
return self
end
function CameraChallenge:PromiseValidViewport()
if self:HasValidViewport() then
return Promise.resolved()
end
if self._maid._currentPromise then
return self._maid._currentPromise
end
local maid = Maid.new()
local promise = Promise.new(function(resolve, reject)
maid:GiveTask(self.LastCamera.Changed:Connect(function()
if self:HasValidViewport() then
resolve(self.LastCamera.Value, self.GoodViewport.Value)
end
end))
maid:GiveTask(self.GoodViewport.Changed:Connect(function()
if self:HasValidViewport() then
resolve(self.LastCamera.Value, self.GoodViewport.Value)
end
end))
maid:GiveTask(reject)
end)
self._maid._currentPromise = promise
promise:Finally(function()
self._maid._currentPromise = nil
maid:DoCleaning()
end)
return promise
end
function CameraChallenge:HasValidViewport()
return self.LastCamera.Value and self.GoodViewport.Value
end
function CameraChallenge:_handleCameraChanged(newCamera, oldCamera, maid)
if not newCamera then
self.GoodViewport.Value = nil
return
end
maid:GiveTask(newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
self:_updateViewport(newCamera)
end))
self:_updateViewport(newCamera)
end
function CameraChallenge:_updateViewport(camera)
if camera.ViewportSize == Vector2.new(1, 1) then
self.GoodViewport.Value = nil
else
self.GoodViewport.Value = camera.ViewportSize
end
end
return CameraChallenge
-- Challenge:
-- 1. Make `camera' always point to the the current or last known camera.
-- a. Problem: CurrentCamera can be nil.
-- b. Guarantee that `camera' is never nil.
-- 2. Make `Goodviewport' always be the current actual size of the Goodviewport.
-- a. Problem: ViewportSize can falsely report (1,1) before it gets fixed up by RenderPrepare.
-- b. Guarantee that `Goodviewport' is never (1,1).
-- 3. Yield until the above guarantees are met.
-- 4. Don't leak memory.
-- See: https://gist.github.com/Fraktality/31bc9cb5abf2af4340cd3a6a775c616d
local Rx = require("Rx")
local RxInstanceUtils = require("RxInstanceUtils")
local function observeChallenge()
return RxInstanceUtils.observeProperty(workspace, "CurrentCamera"):Pipe({
Rx.where(function(value)
return value ~= nil
end);
Rx.switchMap(function(camera)
return RxInstanceUtils.observeProperty(camera, "ViewportSize"):Pipe({
Rx.where(function(viewportSize)
return viewportSize.x ~= 1 and viewportSize.y ~= 1
end);
Rx.map(function(viewport)
return camera, viewport
end)
})
end);
})
end
observeChallenge():Subscribe(function(camera, viewport)
-- this code runs once all conditions are met, and always a valid viewport and camera will be here.
-- note that the camera may be removed/unparented, and we will not emit a new camera and viewport
-- until the next valid viewport and camera are reported.
-- this matches the expectations provided above (i.e. the last known/current valid camera)
print(camera, viewport)
end)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment