mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Conference
This commit is contained in:
parent
550fd89558
commit
a4717b7906
@ -143,6 +143,9 @@ public protocol PresentationCall: AnyObject {
|
|||||||
|
|
||||||
var state: Signal<PresentationCallState, NoError> { get }
|
var state: Signal<PresentationCallState, NoError> { get }
|
||||||
var audioLevel: Signal<Float, NoError> { get }
|
var audioLevel: Signal<Float, NoError> { get }
|
||||||
|
|
||||||
|
var hasConference: Signal<Bool, NoError> { get }
|
||||||
|
var conferenceCall: PresentationGroupCall? { get }
|
||||||
|
|
||||||
var isMuted: Signal<Bool, NoError> { get }
|
var isMuted: Signal<Bool, NoError> { get }
|
||||||
|
|
||||||
@ -164,7 +167,7 @@ public protocol PresentationCall: AnyObject {
|
|||||||
func setCurrentAudioOutput(_ output: AudioSessionOutput)
|
func setCurrentAudioOutput(_ output: AudioSessionOutput)
|
||||||
func debugInfo() -> Signal<(String, String), NoError>
|
func debugInfo() -> Signal<(String, String), NoError>
|
||||||
|
|
||||||
func createConferenceIfPossible()
|
func upgradeToConference(completion: @escaping (PresentationGroupCall) -> Void) -> Disposable
|
||||||
|
|
||||||
func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void)
|
func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void)
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import AccountContext
|
|||||||
import TelegramNotices
|
import TelegramNotices
|
||||||
import AppBundle
|
import AppBundle
|
||||||
import TooltipUI
|
import TooltipUI
|
||||||
|
import CallScreen
|
||||||
|
|
||||||
protocol CallControllerNodeProtocol: AnyObject {
|
protocol CallControllerNodeProtocol: AnyObject {
|
||||||
var isMuted: Bool { get set }
|
var isMuted: Bool { get set }
|
||||||
@ -41,193 +42,6 @@ protocol CallControllerNodeProtocol: AnyObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class CallController: ViewController {
|
public final class CallController: ViewController {
|
||||||
public enum Call: Equatable {
|
|
||||||
case call(PresentationCall)
|
|
||||||
case groupCall(PresentationGroupCall)
|
|
||||||
|
|
||||||
public static func ==(lhs: Call, rhs: Call) -> Bool {
|
|
||||||
switch lhs {
|
|
||||||
case let .call(lhsCall):
|
|
||||||
if case let .call(rhsCall) = rhs {
|
|
||||||
return lhsCall === rhsCall
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case let .groupCall(lhsGroupCall):
|
|
||||||
if case let .groupCall(rhsGroupCall) = rhs {
|
|
||||||
return lhsGroupCall === rhsGroupCall
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var context: AccountContext {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
return call.context
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
return groupCall.accountContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var peerId: EnginePeer.Id? {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
return call.peerId
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
return groupCall.peerId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestVideo() {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
call.requestVideo()
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
groupCall.requestVideo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func disableVideo() {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
call.disableVideo()
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
groupCall.disableVideo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func disableScreencast() {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
(call as? PresentationCallImpl)?.disableScreencast()
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
groupCall.disableScreencast()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func switchVideoCamera() {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
call.switchVideoCamera()
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
groupCall.switchVideoCamera()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func toggleIsMuted() {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
call.toggleIsMuted()
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
groupCall.toggleIsMuted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
call.setCurrentAudioOutput(output)
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
groupCall.setCurrentAudioOutput(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var isMuted: Signal<Bool, NoError> {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
return call.isMuted
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
return groupCall.isMuted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var audioLevel: Signal<Float, NoError> {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
return call.audioLevel
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
var audioLevelId: UInt32?
|
|
||||||
return groupCall.audioLevels |> map { audioLevels -> Float in
|
|
||||||
var result: Float = 0
|
|
||||||
for item in audioLevels {
|
|
||||||
if let audioLevelId {
|
|
||||||
if item.1 == audioLevelId {
|
|
||||||
result = item.2
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if item.1 != 0 {
|
|
||||||
audioLevelId = item.1
|
|
||||||
result = item.2
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var isOutgoing: Bool {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
return call.isOutgoing
|
|
||||||
case .groupCall:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
call.makeOutgoingVideoView(completion: completion)
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
groupCall.makeOutgoingVideoView(requestClone: false, completion: { a, _ in
|
|
||||||
completion(a)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
return call.audioOutputState
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
return groupCall.audioOutputState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func debugInfo() -> Signal<(String, String), NoError> {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
return call.debugInfo()
|
|
||||||
case .groupCall:
|
|
||||||
return .single(("", ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func answer() {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
call.answer()
|
|
||||||
case .groupCall:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func hangUp() -> Signal<Bool, NoError> {
|
|
||||||
switch self {
|
|
||||||
case let .call(call):
|
|
||||||
return call.hangUp()
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
return groupCall.leave(terminateIfPossible: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var controllerNode: CallControllerNodeProtocol {
|
private var controllerNode: CallControllerNodeProtocol {
|
||||||
return self.displayNode as! CallControllerNodeProtocol
|
return self.displayNode as! CallControllerNodeProtocol
|
||||||
}
|
}
|
||||||
@ -242,7 +56,7 @@ public final class CallController: ViewController {
|
|||||||
|
|
||||||
private let sharedContext: SharedAccountContext
|
private let sharedContext: SharedAccountContext
|
||||||
private let account: Account
|
private let account: Account
|
||||||
public let call: CallController.Call
|
public let call: PresentationCall
|
||||||
private let easyDebugAccess: Bool
|
private let easyDebugAccess: Bool
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
@ -268,7 +82,7 @@ public final class CallController: ViewController {
|
|||||||
public var onViewDidAppear: (() -> Void)?
|
public var onViewDidAppear: (() -> Void)?
|
||||||
public var onViewDidDisappear: (() -> Void)?
|
public var onViewDidDisappear: (() -> Void)?
|
||||||
|
|
||||||
public init(sharedContext: SharedAccountContext, account: Account, call: CallController.Call, easyDebugAccess: Bool) {
|
public init(sharedContext: SharedAccountContext, account: Account, call: PresentationCall, easyDebugAccess: Bool) {
|
||||||
self.sharedContext = sharedContext
|
self.sharedContext = sharedContext
|
||||||
self.account = account
|
self.account = account
|
||||||
self.call = call
|
self.call = call
|
||||||
@ -293,84 +107,10 @@ public final class CallController: ViewController {
|
|||||||
|
|
||||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
|
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
|
||||||
|
|
||||||
switch call {
|
self.disposable = (call.state
|
||||||
case let .call(call):
|
|> deliverOnMainQueue).start(next: { [weak self] callState in
|
||||||
self.disposable = (call.state
|
self?.callStateUpdated(callState)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] callState in
|
})
|
||||||
self?.callStateUpdated(callState)
|
|
||||||
})
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
let accountPeerId = groupCall.account.peerId
|
|
||||||
let videoEndpoints: Signal<(local: String?, remote: PresentationGroupCallRequestedVideo?), NoError> = groupCall.members
|
|
||||||
|> map { members -> (local: String?, remote: PresentationGroupCallRequestedVideo?) in
|
|
||||||
guard let members else {
|
|
||||||
return (nil, nil)
|
|
||||||
}
|
|
||||||
var local: String?
|
|
||||||
var remote: PresentationGroupCallRequestedVideo?
|
|
||||||
for participant in members.participants {
|
|
||||||
if let video = participant.requestedPresentationVideoChannel(minQuality: .thumbnail, maxQuality: .full) ?? participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: .full) {
|
|
||||||
if participant.peer.id == accountPeerId {
|
|
||||||
local = video.endpointId
|
|
||||||
} else {
|
|
||||||
if remote == nil {
|
|
||||||
remote = video
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (local, remote)
|
|
||||||
}
|
|
||||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
|
||||||
return lhs == rhs
|
|
||||||
})
|
|
||||||
|
|
||||||
var startTimestamp: Double?
|
|
||||||
self.disposable = (combineLatest(queue: .mainQueue(),
|
|
||||||
groupCall.state,
|
|
||||||
videoEndpoints
|
|
||||||
)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] callState, videoEndpoints in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let mappedState: PresentationCallState.State
|
|
||||||
switch callState.networkState {
|
|
||||||
case .connecting:
|
|
||||||
mappedState = .connecting(nil)
|
|
||||||
case .connected:
|
|
||||||
let timestamp = startTimestamp ?? CFAbsoluteTimeGetCurrent()
|
|
||||||
startTimestamp = timestamp
|
|
||||||
mappedState = .active(timestamp, nil, Data())
|
|
||||||
}
|
|
||||||
|
|
||||||
var mappedLocalVideoState: PresentationCallState.VideoState = .inactive
|
|
||||||
var mappedRemoteVideoState: PresentationCallState.RemoteVideoState = .inactive
|
|
||||||
|
|
||||||
if let local = videoEndpoints.local {
|
|
||||||
mappedLocalVideoState = .active(isScreencast: false, endpointId: local)
|
|
||||||
}
|
|
||||||
if let remote = videoEndpoints.remote {
|
|
||||||
mappedRemoteVideoState = .active(endpointId: remote.endpointId)
|
|
||||||
}
|
|
||||||
|
|
||||||
if case let .groupCall(groupCall) = self.call {
|
|
||||||
var requestedVideo: [PresentationGroupCallRequestedVideo] = []
|
|
||||||
if let remote = videoEndpoints.remote {
|
|
||||||
requestedVideo.append(remote)
|
|
||||||
}
|
|
||||||
groupCall.setRequestedVideoList(items: requestedVideo)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.callStateUpdated(PresentationCallState(
|
|
||||||
state: mappedState,
|
|
||||||
videoState: mappedLocalVideoState,
|
|
||||||
remoteVideoState: mappedRemoteVideoState,
|
|
||||||
remoteAudioState: .active,
|
|
||||||
remoteBatteryLevel: .normal
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
self.callMutedDisposable = (call.isMuted
|
self.callMutedDisposable = (call.isMuted
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
@ -605,11 +345,7 @@ public final class CallController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let callPeerView: Signal<PeerView?, NoError>
|
let callPeerView: Signal<PeerView?, NoError>
|
||||||
if let peerId = self.call.peerId {
|
callPeerView = self.account.postbox.peerView(id: self.call.peerId) |> map(Optional.init)
|
||||||
callPeerView = self.account.postbox.peerView(id: peerId) |> map(Optional.init)
|
|
||||||
} else {
|
|
||||||
callPeerView = .single(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.peerDisposable = (combineLatest(queue: .mainQueue(),
|
self.peerDisposable = (combineLatest(queue: .mainQueue(),
|
||||||
self.account.postbox.peerView(id: self.account.peerId) |> take(1),
|
self.account.postbox.peerView(id: self.account.peerId) |> take(1),
|
||||||
@ -659,6 +395,26 @@ public final class CallController: ViewController {
|
|||||||
self.onViewDidDisappear?()
|
self.onViewDidDisappear?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class AnimateOutToGroupChat {
|
||||||
|
let incomingPeerId: EnginePeer.Id
|
||||||
|
let incomingVideoLayer: CALayer?
|
||||||
|
let incomingVideoPlaceholder: VideoSource.Output?
|
||||||
|
|
||||||
|
init(
|
||||||
|
incomingPeerId: EnginePeer.Id,
|
||||||
|
incomingVideoLayer: CALayer?,
|
||||||
|
incomingVideoPlaceholder: VideoSource.Output?
|
||||||
|
) {
|
||||||
|
self.incomingPeerId = incomingPeerId
|
||||||
|
self.incomingVideoLayer = incomingVideoLayer
|
||||||
|
self.incomingVideoPlaceholder = incomingVideoPlaceholder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOutToGroupChat(completion: @escaping () -> Void) -> AnimateOutToGroupChat? {
|
||||||
|
return (self.controllerNode as? CallControllerNodeV2)?.animateOutToGroupChat(completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
@ -674,7 +430,17 @@ public final class CallController: ViewController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func dismissWithoutAnimation() {
|
||||||
|
self.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
private func conferenceAddParticipant() {
|
private func conferenceAddParticipant() {
|
||||||
|
if "".isEmpty {
|
||||||
|
let _ = self.call.upgradeToConference(completion: { _ in
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let controller = self.call.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(
|
let controller = self.call.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(
|
||||||
context: self.call.context,
|
context: self.call.context,
|
||||||
filter: [.onlyWriteable],
|
filter: [.onlyWriteable],
|
||||||
@ -690,17 +456,13 @@ public final class CallController: ViewController {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard case let .call(call) = self.call else {
|
guard let call = self.call as? PresentationCallImpl else {
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let call = call as? PresentationCallImpl else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = call.requestAddToConference(peerId: peer.id)
|
let _ = call.requestAddToConference(peerId: peer.id)
|
||||||
}
|
}
|
||||||
self.dismiss()
|
|
||||||
|
|
||||||
(self.call.context.sharedContext.mainWindow?.viewController as? NavigationController)?.pushViewController(controller)
|
self.present(controller, in: .current)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func backPressed() {
|
@objc private func backPressed() {
|
||||||
|
@ -32,7 +32,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
private let account: Account
|
private let account: Account
|
||||||
private let presentationData: PresentationData
|
private let presentationData: PresentationData
|
||||||
private let statusBar: StatusBar
|
private let statusBar: StatusBar
|
||||||
private let call: CallController.Call
|
private let call: PresentationCall
|
||||||
|
|
||||||
private let containerView: UIView
|
private let containerView: UIView
|
||||||
private let callScreen: PrivateCallScreen
|
private let callScreen: PrivateCallScreen
|
||||||
@ -91,7 +91,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
statusBar: StatusBar,
|
statusBar: StatusBar,
|
||||||
debugInfo: Signal<(String, String), NoError>,
|
debugInfo: Signal<(String, String), NoError>,
|
||||||
easyDebugAccess: Bool,
|
easyDebugAccess: Bool,
|
||||||
call: CallController.Call
|
call: PresentationCall
|
||||||
) {
|
) {
|
||||||
self.sharedContext = sharedContext
|
self.sharedContext = sharedContext
|
||||||
self.account = account
|
self.account = account
|
||||||
@ -131,13 +131,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
if self.sharedContext.immediateExperimentalUISettings.conferenceCalls {
|
|
||||||
self.conferenceAddParticipant?()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
self.call.toggleIsMuted()
|
self.call.toggleIsMuted()
|
||||||
}
|
}
|
||||||
self.callScreen.endCallAction = { [weak self] in
|
self.callScreen.endCallAction = { [weak self] in
|
||||||
@ -321,11 +314,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
case .active:
|
case .active:
|
||||||
switch callState.videoState {
|
switch callState.videoState {
|
||||||
case .active(let isScreencast, _), .paused(let isScreencast, _):
|
case .active(let isScreencast, _), .paused(let isScreencast, _):
|
||||||
if isScreencast {
|
let _ = isScreencast
|
||||||
self.call.disableScreencast()
|
self.call.disableVideo()
|
||||||
} else {
|
|
||||||
self.call.disableVideo()
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
DeviceAccess.authorizeAccess(to: .camera(.videoCall), onlyCheck: true, presentationData: self.presentationData, present: { [weak self] c, a in
|
DeviceAccess.authorizeAccess(to: .camera(.videoCall), onlyCheck: true, presentationData: self.presentationData, present: { [weak self] c, a in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -501,22 +491,13 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
self.remoteVideo = nil
|
self.remoteVideo = nil
|
||||||
default:
|
default:
|
||||||
switch callState.videoState {
|
switch callState.videoState {
|
||||||
case .active(let isScreencast, let endpointId), .paused(let isScreencast, let endpointId):
|
case .active(let isScreencast, _), .paused(let isScreencast, _):
|
||||||
if isScreencast {
|
if isScreencast {
|
||||||
self.localVideo = nil
|
self.localVideo = nil
|
||||||
} else {
|
} else {
|
||||||
if self.localVideo == nil {
|
if self.localVideo == nil {
|
||||||
switch self.call {
|
if let call = self.call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: false) {
|
||||||
case let .call(call):
|
self.localVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
|
||||||
if let call = call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: false) {
|
|
||||||
self.localVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
|
|
||||||
}
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
if let groupCall = groupCall as? PresentationGroupCallImpl {
|
|
||||||
if let videoStreamSignal = groupCall.video(endpointId: endpointId) {
|
|
||||||
self.localVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -525,19 +506,10 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch callState.remoteVideoState {
|
switch callState.remoteVideoState {
|
||||||
case .active(let endpointId), .paused(let endpointId):
|
case .active, .paused:
|
||||||
if self.remoteVideo == nil {
|
if self.remoteVideo == nil {
|
||||||
switch self.call {
|
if let call = self.call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: true) {
|
||||||
case let .call(call):
|
self.remoteVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
|
||||||
if let call = call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: true) {
|
|
||||||
self.remoteVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
|
|
||||||
}
|
|
||||||
case let .groupCall(groupCall):
|
|
||||||
if let groupCall = groupCall as? PresentationGroupCallImpl {
|
|
||||||
if let videoStreamSignal = groupCall.video(endpointId: endpointId) {
|
|
||||||
self.remoteVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .inactive:
|
case .inactive:
|
||||||
@ -710,6 +682,17 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateOutToGroupChat(completion: @escaping () -> Void) -> CallController.AnimateOutToGroupChat {
|
||||||
|
self.callScreen.animateOutToGroupChat(completion: completion)
|
||||||
|
|
||||||
|
let takenIncomingVideoLayer = self.callScreen.takeIncomingVideoLayer()
|
||||||
|
return CallController.AnimateOutToGroupChat(
|
||||||
|
incomingPeerId: self.call.peerId,
|
||||||
|
incomingVideoLayer: takenIncomingVideoLayer?.0,
|
||||||
|
incomingVideoPlaceholder: takenIncomingVideoLayer?.1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func expandFromPipIfPossible() {
|
func expandFromPipIfPossible() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
|
|
||||||
private let audioLevelDisposable = MetaDisposable()
|
private let audioLevelDisposable = MetaDisposable()
|
||||||
private let stateDisposable = MetaDisposable()
|
private let stateDisposable = MetaDisposable()
|
||||||
private var didSetupData = false
|
private weak var didSetupDataForCall: AnyObject?
|
||||||
|
|
||||||
private var currentSize: CGSize?
|
private var currentSize: CGSize?
|
||||||
private var currentContent: Content?
|
private var currentContent: Content?
|
||||||
@ -277,8 +277,16 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
|
|
||||||
let wasEmpty = (self.titleNode.attributedText?.string ?? "").isEmpty
|
let wasEmpty = (self.titleNode.attributedText?.string ?? "").isEmpty
|
||||||
|
|
||||||
if !self.didSetupData {
|
let setupDataForCall: AnyObject?
|
||||||
self.didSetupData = true
|
switch content {
|
||||||
|
case let .call(_, _, call):
|
||||||
|
setupDataForCall = call
|
||||||
|
case let .groupCall(_, _, call):
|
||||||
|
setupDataForCall = call
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.didSetupDataForCall !== setupDataForCall {
|
||||||
|
self.didSetupDataForCall = setupDataForCall
|
||||||
switch content {
|
switch content {
|
||||||
case let .call(sharedContext, account, call):
|
case let .call(sharedContext, account, call):
|
||||||
self.presentationData = sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||||
|
@ -131,20 +131,33 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
private let screencastAudioDataDisposable = MetaDisposable()
|
private let screencastAudioDataDisposable = MetaDisposable()
|
||||||
private let screencastStateDisposable = MetaDisposable()
|
private let screencastStateDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var conferenceCall: PresentationGroupCallImpl?
|
private var conferenceCallImpl: PresentationGroupCallImpl?
|
||||||
|
public var conferenceCall: PresentationGroupCall? {
|
||||||
|
if !self.hasConferenceValue {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.conferenceCallImpl
|
||||||
|
}
|
||||||
private var conferenceCallDisposable: Disposable?
|
private var conferenceCallDisposable: Disposable?
|
||||||
|
private var upgradedToConferenceCompletions = Bag<(PresentationGroupCall) -> Void>()
|
||||||
|
|
||||||
|
private var waitForConferenceCallReadyDisposable: Disposable?
|
||||||
|
private let hasConferencePromise = ValuePromise<Bool>(false)
|
||||||
|
private var hasConferenceValue: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if self.hasConferenceValue != oldValue {
|
||||||
|
self.hasConferencePromise.set(self.hasConferenceValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public var hasConference: Signal<Bool, NoError> {
|
||||||
|
return self.hasConferencePromise.get()
|
||||||
|
}
|
||||||
|
|
||||||
private var localVideoEndpointId: String?
|
private var localVideoEndpointId: String?
|
||||||
private var remoteVideoEndpointId: String?
|
private var remoteVideoEndpointId: String?
|
||||||
|
|
||||||
private var conferenceSignalingDataDisposable: Disposable?
|
|
||||||
private var conferenceIsConnected: Bool = false
|
|
||||||
private var notifyConferenceIsConnectedTimer: Foundation.Timer?
|
|
||||||
|
|
||||||
private var remoteConferenceIsConnectedTimestamp: Double?
|
|
||||||
private let remoteConferenceIsConnected = ValuePromise<Bool>(false, ignoreRepeated: true)
|
|
||||||
private var remoteConferenceIsConnectedTimer: Foundation.Timer?
|
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
audioSession: ManagedAudioSession,
|
audioSession: ManagedAudioSession,
|
||||||
@ -340,9 +353,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
self.ongoingContextStateDisposable?.dispose()
|
self.ongoingContextStateDisposable?.dispose()
|
||||||
self.ongoingContextIsFailedDisposable?.dispose()
|
self.ongoingContextIsFailedDisposable?.dispose()
|
||||||
self.ongoingContextIsDroppedDisposable?.dispose()
|
self.ongoingContextIsDroppedDisposable?.dispose()
|
||||||
self.notifyConferenceIsConnectedTimer?.invalidate()
|
self.waitForConferenceCallReadyDisposable?.dispose()
|
||||||
self.conferenceSignalingDataDisposable?.dispose()
|
|
||||||
self.remoteConferenceIsConnectedTimer?.invalidate()
|
|
||||||
|
|
||||||
if let dropCallKitCallTimer = self.dropCallKitCallTimer {
|
if let dropCallKitCallTimer = self.dropCallKitCallTimer {
|
||||||
dropCallKitCallTimer.invalidate()
|
dropCallKitCallTimer.invalidate()
|
||||||
@ -540,13 +551,13 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
self.callWasActive = true
|
self.callWasActive = true
|
||||||
|
|
||||||
var isConference = false
|
var isConference = false
|
||||||
if case let .active(_, _, _, _, _, version, _, _, _) = sessionState.state {
|
if case let .active(_, _, _, _, _, _, _, _, conferenceCall) = sessionState.state {
|
||||||
isConference = version == "13.0.0"
|
isConference = conferenceCall != nil
|
||||||
} else if case .switchedToConference = sessionState.state {
|
} else if case .switchedToConference = sessionState.state {
|
||||||
isConference = true
|
isConference = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if let callContextState = callContextState {
|
if let callContextState = callContextState, !isConference {
|
||||||
switch callContextState.state {
|
switch callContextState.state {
|
||||||
case .initializing:
|
case .initializing:
|
||||||
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||||
@ -593,221 +604,290 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
|
|
||||||
if let (key, keyVisualHash, conferenceCall) = conferenceCallData {
|
if let (key, keyVisualHash, conferenceCall) = conferenceCallData {
|
||||||
if self.conferenceCallDisposable == nil {
|
if self.conferenceCallDisposable == nil {
|
||||||
presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
self.conferenceCallDisposable = EmptyDisposable
|
||||||
|
|
||||||
|
self.ongoingContextStateDisposable?.dispose()
|
||||||
|
self.ongoingContextStateDisposable = nil
|
||||||
|
self.ongoingContext?.stop(debugLogValue: Promise())
|
||||||
|
self.ongoingContext = nil
|
||||||
|
|
||||||
self.conferenceCallDisposable = (self.context.engine.calls.getCurrentGroupCall(callId: conferenceCall.id, accessHash: conferenceCall.accessHash)
|
let conferenceCall = PresentationGroupCallImpl(
|
||||||
|> delay(sessionState.isOutgoing ? 0.0 : 2.0, queue: .mainQueue())
|
accountContext: self.context,
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
audioSession: self.audioSession,
|
||||||
guard let self, let result else {
|
callKitIntegration: self.callKitIntegration,
|
||||||
|
getDeviceAccessData: self.getDeviceAccessData,
|
||||||
|
initialCall: EngineGroupCallDescription(
|
||||||
|
id: conferenceCall.id,
|
||||||
|
accessHash: conferenceCall.accessHash,
|
||||||
|
title: nil,
|
||||||
|
scheduleTimestamp: nil,
|
||||||
|
subscribedToScheduled: false,
|
||||||
|
isStream: false
|
||||||
|
),
|
||||||
|
internalId: CallSessionInternalId(),
|
||||||
|
peerId: nil,
|
||||||
|
isChannel: false,
|
||||||
|
invite: nil,
|
||||||
|
joinAsPeerId: nil,
|
||||||
|
isStream: false,
|
||||||
|
encryptionKey: (key, 1),
|
||||||
|
conferenceFromCallId: conferenceFromCallId,
|
||||||
|
isConference: true,
|
||||||
|
sharedAudioDevice: self.sharedAudioDevice
|
||||||
|
)
|
||||||
|
self.conferenceCallImpl = conferenceCall
|
||||||
|
|
||||||
|
conferenceCall.setIsMuted(action: self.isMutedValue ? .muted(isPushToTalkActive: false) : .unmuted)
|
||||||
|
if let videoCapturer = self.videoCapturer {
|
||||||
|
conferenceCall.requestVideo(capturer: videoCapturer)
|
||||||
|
}
|
||||||
|
|
||||||
|
let accountPeerId = conferenceCall.account.peerId
|
||||||
|
let videoEndpoints: Signal<(local: String?, remote: PresentationGroupCallRequestedVideo?), NoError> = conferenceCall.members
|
||||||
|
|> map { members -> (local: String?, remote: PresentationGroupCallRequestedVideo?) in
|
||||||
|
guard let members else {
|
||||||
|
return (nil, nil)
|
||||||
|
}
|
||||||
|
var local: String?
|
||||||
|
var remote: PresentationGroupCallRequestedVideo?
|
||||||
|
for participant in members.participants {
|
||||||
|
if let video = participant.requestedPresentationVideoChannel(minQuality: .thumbnail, maxQuality: .full) ?? participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: .full) {
|
||||||
|
if participant.peer.id == accountPeerId {
|
||||||
|
local = video.endpointId
|
||||||
|
} else {
|
||||||
|
if remote == nil {
|
||||||
|
remote = video
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (local, remote)
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||||
|
return lhs == rhs
|
||||||
|
})
|
||||||
|
|
||||||
|
var startTimestamp: Double?
|
||||||
|
self.ongoingContextStateDisposable = (combineLatest(queue: .mainQueue(),
|
||||||
|
conferenceCall.state,
|
||||||
|
videoEndpoints,
|
||||||
|
conferenceCall.signalBars,
|
||||||
|
conferenceCall.isFailed
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { [weak self] callState, videoEndpoints, signalBars, isFailed in
|
||||||
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let conferenceCall = PresentationGroupCallImpl(
|
var mappedLocalVideoState: PresentationCallState.VideoState = .inactive
|
||||||
accountContext: self.context,
|
var mappedRemoteVideoState: PresentationCallState.RemoteVideoState = .inactive
|
||||||
audioSession: self.audioSession,
|
|
||||||
callKitIntegration: self.callKitIntegration,
|
|
||||||
getDeviceAccessData: self.getDeviceAccessData,
|
|
||||||
initialCall: EngineGroupCallDescription(
|
|
||||||
id: result.info.id,
|
|
||||||
accessHash: result.info.accessHash,
|
|
||||||
title: nil,
|
|
||||||
scheduleTimestamp: nil,
|
|
||||||
subscribedToScheduled: false,
|
|
||||||
isStream: false
|
|
||||||
),
|
|
||||||
internalId: CallSessionInternalId(),
|
|
||||||
peerId: nil,
|
|
||||||
isChannel: false,
|
|
||||||
invite: nil,
|
|
||||||
joinAsPeerId: nil,
|
|
||||||
isStream: false,
|
|
||||||
encryptionKey: (key, 1),
|
|
||||||
conferenceFromCallId: conferenceFromCallId,
|
|
||||||
isConference: true,
|
|
||||||
sharedAudioDevice: self.sharedAudioDevice
|
|
||||||
)
|
|
||||||
self.conferenceCall = conferenceCall
|
|
||||||
|
|
||||||
conferenceCall.setIsMuted(action: .muted(isPushToTalkActive: !self.isMutedValue))
|
if let local = videoEndpoints.local {
|
||||||
|
mappedLocalVideoState = .active(isScreencast: false, endpointId: local)
|
||||||
|
}
|
||||||
|
if let remote = videoEndpoints.remote {
|
||||||
|
mappedRemoteVideoState = .active(endpointId: remote.endpointId)
|
||||||
|
}
|
||||||
|
|
||||||
let accountPeerId = conferenceCall.account.peerId
|
self.localVideoEndpointId = videoEndpoints.local
|
||||||
let videoEndpoints: Signal<(local: String?, remote: PresentationGroupCallRequestedVideo?), NoError> = conferenceCall.members
|
self.remoteVideoEndpointId = videoEndpoints.remote?.endpointId
|
||||||
|> map { members -> (local: String?, remote: PresentationGroupCallRequestedVideo?) in
|
|
||||||
guard let members else {
|
if let conferenceCall = self.conferenceCall {
|
||||||
return (nil, nil)
|
var requestedVideo: [PresentationGroupCallRequestedVideo] = []
|
||||||
|
if let remote = videoEndpoints.remote {
|
||||||
|
requestedVideo.append(remote)
|
||||||
}
|
}
|
||||||
var local: String?
|
conferenceCall.setRequestedVideoList(items: requestedVideo)
|
||||||
var remote: PresentationGroupCallRequestedVideo?
|
}
|
||||||
for participant in members.participants {
|
|
||||||
if let video = participant.requestedPresentationVideoChannel(minQuality: .thumbnail, maxQuality: .full) ?? participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: .full) {
|
let mappedState: PresentationCallState.State
|
||||||
if participant.peer.id == accountPeerId {
|
if isFailed {
|
||||||
local = video.endpointId
|
mappedState = .terminating(.error(.disconnected))
|
||||||
} else {
|
} else {
|
||||||
if remote == nil {
|
switch callState.networkState {
|
||||||
remote = video
|
case .connecting:
|
||||||
|
mappedState = .connecting(keyVisualHash)
|
||||||
|
case .connected:
|
||||||
|
let timestamp = startTimestamp ?? CFAbsoluteTimeGetCurrent()
|
||||||
|
startTimestamp = timestamp
|
||||||
|
mappedState = .active(timestamp, signalBars, keyVisualHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.didDropCall && !self.droppedCall {
|
||||||
|
/*let presentationState = PresentationCallState(
|
||||||
|
state: mappedState,
|
||||||
|
videoState: mappedLocalVideoState,
|
||||||
|
remoteVideoState: mappedRemoteVideoState,
|
||||||
|
remoteAudioState: .active,
|
||||||
|
remoteBatteryLevel: .normal
|
||||||
|
)*/
|
||||||
|
let _ = mappedState
|
||||||
|
|
||||||
|
let timestamp: Double
|
||||||
|
if let activeTimestamp = self.activeTimestamp {
|
||||||
|
timestamp = activeTimestamp
|
||||||
|
} else {
|
||||||
|
timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
self.activeTimestamp = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedLocalVideoState = .inactive
|
||||||
|
mappedRemoteVideoState = .inactive
|
||||||
|
if self.videoCapturer != nil {
|
||||||
|
mappedLocalVideoState = .active(isScreencast: false, endpointId: "local")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let callContextState = self.callContextState {
|
||||||
|
switch callContextState.remoteVideoState {
|
||||||
|
case .active, .paused:
|
||||||
|
mappedRemoteVideoState = .active(endpointId: "temp-\(self.peerId.toInt64())")
|
||||||
|
case .inactive:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationState = PresentationCallState(
|
||||||
|
state: .active(timestamp, signalBars, keyVisualHash),
|
||||||
|
videoState: mappedLocalVideoState,
|
||||||
|
remoteVideoState: mappedRemoteVideoState,
|
||||||
|
remoteAudioState: .active,
|
||||||
|
remoteBatteryLevel: .normal
|
||||||
|
)
|
||||||
|
self.statePromise.set(presentationState)
|
||||||
|
self.updateTone(presentationState, callContextState: nil, previous: nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.ongoingContextIsFailedDisposable = (conferenceCall.isFailed
|
||||||
|
|> filter { $0 }
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.didDropCall {
|
||||||
|
self.didDropCall = true
|
||||||
|
self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.ongoingContextIsDroppedDisposable = (conferenceCall.canBeRemoved
|
||||||
|
|> filter { $0 }
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.didDropCall {
|
||||||
|
self.didDropCall = true
|
||||||
|
self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: .single(nil))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var audioLevelId: UInt32?
|
||||||
|
let audioLevel = conferenceCall.audioLevels |> map { audioLevels -> Float in
|
||||||
|
var result: Float = 0
|
||||||
|
for item in audioLevels {
|
||||||
|
if let audioLevelId {
|
||||||
|
if item.1 == audioLevelId {
|
||||||
|
result = item.2
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if item.1 != 0 {
|
||||||
|
audioLevelId = item.1
|
||||||
|
result = item.2
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
self.audioLevelDisposable = (audioLevel
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] level in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.audioLevelPromise.set(level)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let upgradedToConferenceCompletions = self.upgradedToConferenceCompletions.copyItems()
|
||||||
|
self.upgradedToConferenceCompletions.removeAll()
|
||||||
|
for f in upgradedToConferenceCompletions {
|
||||||
|
f(conferenceCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
let waitForLocalVideo = self.videoCapturer != nil
|
||||||
|
|
||||||
|
let waitForRemotePeerId: EnginePeer.Id? = self.peerId
|
||||||
|
var waitForRemoteVideo: EnginePeer.Id?
|
||||||
|
if let callContextState = self.callContextState {
|
||||||
|
switch callContextState.remoteVideoState {
|
||||||
|
case .active, .paused:
|
||||||
|
waitForRemoteVideo = self.peerId
|
||||||
|
case .inactive:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.waitForConferenceCallReadyDisposable?.dispose()
|
||||||
|
self.waitForConferenceCallReadyDisposable = (combineLatest(queue: .mainQueue(),
|
||||||
|
conferenceCall.state,
|
||||||
|
conferenceCall.members
|
||||||
|
)
|
||||||
|
|> filter { state, members in
|
||||||
|
if state.networkState != .connected {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let waitForRemotePeerId {
|
||||||
|
var found = false
|
||||||
|
if let members {
|
||||||
|
for participant in members.participants {
|
||||||
|
if participant.peer.id == waitForRemotePeerId {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if waitForLocalVideo {
|
||||||
|
if let members {
|
||||||
|
for participant in members.participants {
|
||||||
|
if participant.peer.id == state.myPeerId {
|
||||||
|
if participant.videoDescription == nil {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (local, remote)
|
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
if let waitForRemoteVideo {
|
||||||
return lhs == rhs
|
if let members {
|
||||||
})
|
for participant in members.participants {
|
||||||
|
if participant.peer.id == waitForRemoteVideo {
|
||||||
let remoteIsConnectedAggregated = combineLatest(queue: .mainQueue(),
|
if participant.videoDescription == nil {
|
||||||
self.remoteConferenceIsConnected.get(),
|
return false
|
||||||
conferenceCall.hasActiveIncomingData
|
}
|
||||||
)
|
|
||||||
|> map { remoteConferenceIsConnected, hasActiveIncomingData -> Bool in
|
|
||||||
return remoteConferenceIsConnected || hasActiveIncomingData
|
|
||||||
}
|
|
||||||
|> distinctUntilChanged
|
|
||||||
|
|
||||||
var startTimestamp: Double?
|
|
||||||
self.ongoingContextStateDisposable = (combineLatest(queue: .mainQueue(),
|
|
||||||
conferenceCall.state,
|
|
||||||
videoEndpoints,
|
|
||||||
conferenceCall.signalBars,
|
|
||||||
conferenceCall.isFailed,
|
|
||||||
remoteIsConnectedAggregated
|
|
||||||
)
|
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] callState, videoEndpoints, signalBars, isFailed, remoteIsConnectedAggregated in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var mappedLocalVideoState: PresentationCallState.VideoState = .inactive
|
|
||||||
var mappedRemoteVideoState: PresentationCallState.RemoteVideoState = .inactive
|
|
||||||
|
|
||||||
if let local = videoEndpoints.local {
|
|
||||||
mappedLocalVideoState = .active(isScreencast: false, endpointId: local)
|
|
||||||
}
|
|
||||||
if let remote = videoEndpoints.remote {
|
|
||||||
mappedRemoteVideoState = .active(endpointId: remote.endpointId)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.localVideoEndpointId = videoEndpoints.local
|
|
||||||
self.remoteVideoEndpointId = videoEndpoints.remote?.endpointId
|
|
||||||
|
|
||||||
if let conferenceCall = self.conferenceCall {
|
|
||||||
var requestedVideo: [PresentationGroupCallRequestedVideo] = []
|
|
||||||
if let remote = videoEndpoints.remote {
|
|
||||||
requestedVideo.append(remote)
|
|
||||||
}
|
|
||||||
conferenceCall.setRequestedVideoList(items: requestedVideo)
|
|
||||||
}
|
|
||||||
|
|
||||||
var isConnected = false
|
|
||||||
let mappedState: PresentationCallState.State
|
|
||||||
if isFailed {
|
|
||||||
mappedState = .terminating(.error(.disconnected))
|
|
||||||
} else {
|
|
||||||
switch callState.networkState {
|
|
||||||
case .connecting:
|
|
||||||
mappedState = .connecting(keyVisualHash)
|
|
||||||
case .connected:
|
|
||||||
isConnected = true
|
|
||||||
if remoteIsConnectedAggregated {
|
|
||||||
let timestamp = startTimestamp ?? CFAbsoluteTimeGetCurrent()
|
|
||||||
startTimestamp = timestamp
|
|
||||||
mappedState = .active(timestamp, signalBars, keyVisualHash)
|
|
||||||
} else {
|
|
||||||
mappedState = .connecting(keyVisualHash)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateConferenceIsConnected(isConnected: isConnected)
|
|
||||||
|
|
||||||
if !self.didDropCall && !self.droppedCall {
|
|
||||||
let presentationState = PresentationCallState(
|
|
||||||
state: mappedState,
|
|
||||||
videoState: mappedLocalVideoState,
|
|
||||||
remoteVideoState: mappedRemoteVideoState,
|
|
||||||
remoteAudioState: .active,
|
|
||||||
remoteBatteryLevel: .normal
|
|
||||||
)
|
|
||||||
self.statePromise.set(presentationState)
|
|
||||||
self.updateTone(presentationState, callContextState: nil, previous: nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.ongoingContextIsFailedDisposable = (conferenceCall.isFailed
|
|
||||||
|> filter { $0 }
|
|
||||||
|> take(1)
|
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !self.didDropCall {
|
|
||||||
self.didDropCall = true
|
|
||||||
self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.ongoingContextIsDroppedDisposable = (conferenceCall.canBeRemoved
|
|
||||||
|> filter { $0 }
|
|
||||||
|> take(1)
|
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !self.didDropCall {
|
|
||||||
self.didDropCall = true
|
|
||||||
self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: .single(nil))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var audioLevelId: UInt32?
|
|
||||||
let audioLevel = conferenceCall.audioLevels |> map { audioLevels -> Float in
|
|
||||||
var result: Float = 0
|
|
||||||
for item in audioLevels {
|
|
||||||
if let audioLevelId {
|
|
||||||
if item.1 == audioLevelId {
|
|
||||||
result = item.2
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if item.1 != 0 {
|
|
||||||
audioLevelId = item.1
|
|
||||||
result = item.2
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
self.audioLevelDisposable = (audioLevel
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] level in
|
|> map { _, _ -> Void in
|
||||||
if let strongSelf = self {
|
return Void()
|
||||||
strongSelf.audioLevelPromise.set(level)
|
}
|
||||||
}
|
|> take(1)
|
||||||
})
|
|> timeout(10.0, queue: .mainQueue(), alternate: .single(Void()))).start(next: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
let localIsConnected = conferenceCall.state
|
return
|
||||||
|> map { state -> Bool in
|
|
||||||
switch state.networkState {
|
|
||||||
case .connected:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
self.hasConferenceValue = true
|
||||||
|
|
||||||
let bothLocalAndRemoteConnected = combineLatest(queue: .mainQueue(),
|
|
||||||
localIsConnected,
|
|
||||||
remoteIsConnectedAggregated
|
|
||||||
)
|
|
||||||
|> map { localIsConnected, remoteIsConnectedAggregated -> Bool in
|
|
||||||
return localIsConnected && remoteIsConnectedAggregated
|
|
||||||
}
|
|
||||||
|> distinctUntilChanged
|
|
||||||
|
|
||||||
conferenceCall.internal_isRemoteConnected.set(bothLocalAndRemoteConnected)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -817,26 +897,10 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
if let _ = audioSessionControl {
|
if let _ = audioSessionControl {
|
||||||
self.audioSessionShouldBeActive.set(true)
|
self.audioSessionShouldBeActive.set(true)
|
||||||
}
|
}
|
||||||
case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall):
|
case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P, _):
|
||||||
if conferenceCall == nil, version == "13.0.0" {
|
|
||||||
self.createConferenceIfPossible()
|
|
||||||
}
|
|
||||||
|
|
||||||
self.audioSessionShouldBeActive.set(true)
|
self.audioSessionShouldBeActive.set(true)
|
||||||
|
|
||||||
if version == "13.0.0" && self.conferenceSignalingDataDisposable == nil {
|
if conferenceCallData != nil {
|
||||||
self.conferenceSignalingDataDisposable = self.context.account.callSessionManager.beginReceivingCallSignalingData(internalId: self.internalId, { [weak self] dataList in
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.processConferenceSignalingData(dataList: dataList)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if version == "13.0.0" || conferenceCallData != nil {
|
|
||||||
if sessionState.isOutgoing {
|
if sessionState.isOutgoing {
|
||||||
self.callKitIntegration?.reportOutgoingCallConnected(uuid: sessionState.id, at: Date())
|
self.callKitIntegration?.reportOutgoingCallConnected(uuid: sessionState.id, at: Date())
|
||||||
}
|
}
|
||||||
@ -910,7 +974,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
if wasActive {
|
if wasActive {
|
||||||
let debugLogValue = Promise<String?>()
|
let debugLogValue = Promise<String?>()
|
||||||
self.ongoingContext?.stop(sendDebugLogs: options.contains(.sendDebugLogs), debugLogValue: debugLogValue)
|
self.ongoingContext?.stop(sendDebugLogs: options.contains(.sendDebugLogs), debugLogValue: debugLogValue)
|
||||||
let _ = self.conferenceCall?.leave(terminateIfPossible: false).start()
|
let _ = self.conferenceCallImpl?.leave(terminateIfPossible: false).start()
|
||||||
}
|
}
|
||||||
case .dropping:
|
case .dropping:
|
||||||
break
|
break
|
||||||
@ -918,7 +982,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
self.audioSessionShouldBeActive.set(false)
|
self.audioSessionShouldBeActive.set(false)
|
||||||
if wasActive {
|
if wasActive {
|
||||||
let debugLogValue = Promise<String?>()
|
let debugLogValue = Promise<String?>()
|
||||||
if let conferenceCall = self.conferenceCall {
|
if let conferenceCall = self.conferenceCallImpl {
|
||||||
debugLogValue.set(conferenceCall.debugLog.get())
|
debugLogValue.set(conferenceCall.debugLog.get())
|
||||||
let _ = conferenceCall.leave(terminateIfPossible: false).start()
|
let _ = conferenceCall.leave(terminateIfPossible: false).start()
|
||||||
} else {
|
} else {
|
||||||
@ -1064,88 +1128,6 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateConferenceIsConnected(isConnected: Bool) {
|
|
||||||
if self.conferenceIsConnected != isConnected {
|
|
||||||
self.conferenceIsConnected = isConnected
|
|
||||||
self.sendConferenceIsConnectedState()
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.notifyConferenceIsConnectedTimer == nil {
|
|
||||||
self.notifyConferenceIsConnectedTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [weak self] _ in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.sendConferenceIsConnectedState()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func sendConferenceIsConnectedState() {
|
|
||||||
self.sendConferenceSignalingMessage(dict: ["_$": "s", "c": self.conferenceIsConnected])
|
|
||||||
}
|
|
||||||
|
|
||||||
private func processConferenceSignalingData(dataList: [Data]) {
|
|
||||||
for data in dataList {
|
|
||||||
if let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
||||||
self.processConferenceSignalingMessage(dict: dict)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func processConferenceSignalingMessage(dict: [String: Any]) {
|
|
||||||
if let type = dict["_$"] as? String {
|
|
||||||
switch type {
|
|
||||||
case "s":
|
|
||||||
let isConnected = dict["c"] as? Bool ?? false
|
|
||||||
self.remoteConferenceIsConnected.set(isConnected)
|
|
||||||
|
|
||||||
if isConnected {
|
|
||||||
self.remoteConferenceIsConnectedTimestamp = CFAbsoluteTimeGetCurrent()
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.remoteConferenceIsConnectedTimer == nil && isConnected {
|
|
||||||
self.remoteConferenceIsConnectedTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
|
||||||
if let remoteConferenceIsConnectedTimestamp = self.remoteConferenceIsConnectedTimestamp {
|
|
||||||
if remoteConferenceIsConnectedTimestamp + 4.0 < timestamp {
|
|
||||||
self.remoteConferenceIsConnected.set(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if remoteConferenceIsConnectedTimestamp + 10.0 < timestamp {
|
|
||||||
if !self.didDropCall {
|
|
||||||
self.didDropCall = true
|
|
||||||
|
|
||||||
let presentationState = PresentationCallState(
|
|
||||||
state: .terminating(.error(.disconnected)),
|
|
||||||
videoState: .inactive,
|
|
||||||
remoteVideoState: .inactive,
|
|
||||||
remoteAudioState: .active,
|
|
||||||
remoteBatteryLevel: .normal
|
|
||||||
)
|
|
||||||
self.statePromise.set(presentationState)
|
|
||||||
self.updateTone(presentationState, callContextState: nil, previous: nil)
|
|
||||||
|
|
||||||
self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func sendConferenceSignalingMessage(dict: [String: Any]) {
|
|
||||||
if let data = try? JSONSerialization.data(withJSONObject: dict) {
|
|
||||||
self.context.account.callSessionManager.sendSignalingData(internalId: self.internalId, data: data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateIsAudioSessionActive(_ value: Bool) {
|
private func updateIsAudioSessionActive(_ value: Bool) {
|
||||||
if self.isAudioSessionActive != value {
|
if self.isAudioSessionActive != value {
|
||||||
self.isAudioSessionActive = value
|
self.isAudioSessionActive = value
|
||||||
@ -1202,7 +1184,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
public func hangUp() -> Signal<Bool, NoError> {
|
public func hangUp() -> Signal<Bool, NoError> {
|
||||||
let debugLogValue = Promise<String?>()
|
let debugLogValue = Promise<String?>()
|
||||||
self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: debugLogValue.get())
|
self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: debugLogValue.get())
|
||||||
if let conferenceCall = self.conferenceCall {
|
if let conferenceCall = self.conferenceCallImpl {
|
||||||
debugLogValue.set(conferenceCall.debugLog.get())
|
debugLogValue.set(conferenceCall.debugLog.get())
|
||||||
let _ = conferenceCall.leave(terminateIfPossible: false).start()
|
let _ = conferenceCall.leave(terminateIfPossible: false).start()
|
||||||
} else {
|
} else {
|
||||||
@ -1215,7 +1197,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
public func rejectBusy() {
|
public func rejectBusy() {
|
||||||
self.callSessionManager.drop(internalId: self.internalId, reason: .busy, debugLog: .single(nil))
|
self.callSessionManager.drop(internalId: self.internalId, reason: .busy, debugLog: .single(nil))
|
||||||
let debugLog = Promise<String?>()
|
let debugLog = Promise<String?>()
|
||||||
if let conferenceCall = self.conferenceCall {
|
if let conferenceCall = self.conferenceCallImpl {
|
||||||
debugLog.set(conferenceCall.debugLog.get())
|
debugLog.set(conferenceCall.debugLog.get())
|
||||||
let _ = conferenceCall.leave(terminateIfPossible: false).start()
|
let _ = conferenceCall.leave(terminateIfPossible: false).start()
|
||||||
} else {
|
} else {
|
||||||
@ -1231,7 +1213,6 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
self.isMutedValue = value
|
self.isMutedValue = value
|
||||||
self.isMutedPromise.set(self.isMutedValue)
|
self.isMutedPromise.set(self.isMutedValue)
|
||||||
self.ongoingContext?.setIsMuted(self.isMutedValue)
|
self.ongoingContext?.setIsMuted(self.isMutedValue)
|
||||||
self.conferenceCall?.setIsMuted(action: .muted(isPushToTalkActive: !self.isMutedValue))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestVideo() {
|
public func requestVideo() {
|
||||||
@ -1242,7 +1223,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
if let videoCapturer = self.videoCapturer {
|
if let videoCapturer = self.videoCapturer {
|
||||||
if let ongoingContext = self.ongoingContext {
|
if let ongoingContext = self.ongoingContext {
|
||||||
ongoingContext.requestVideo(videoCapturer)
|
ongoingContext.requestVideo(videoCapturer)
|
||||||
} else if let conferenceCall = self.conferenceCall {
|
} else if let conferenceCall = self.conferenceCallImpl {
|
||||||
conferenceCall.requestVideo(capturer: videoCapturer)
|
conferenceCall.requestVideo(capturer: videoCapturer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1258,7 +1239,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
self.videoCapturer = nil
|
self.videoCapturer = nil
|
||||||
if let ongoingContext = self.ongoingContext {
|
if let ongoingContext = self.ongoingContext {
|
||||||
ongoingContext.disableVideo()
|
ongoingContext.disableVideo()
|
||||||
} else if let conferenceCall = self.conferenceCall {
|
} else if let conferenceCall = self.conferenceCallImpl {
|
||||||
conferenceCall.disableVideo()
|
conferenceCall.disableVideo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1308,7 +1289,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
self.isScreencastActive = true
|
self.isScreencastActive = true
|
||||||
if let ongoingContext = self.ongoingContext {
|
if let ongoingContext = self.ongoingContext {
|
||||||
ongoingContext.requestVideo(screencastCapturer)
|
ongoingContext.requestVideo(screencastCapturer)
|
||||||
} else if let conferenceCall = self.conferenceCall {
|
} else if let conferenceCall = self.conferenceCallImpl {
|
||||||
conferenceCall.requestVideo(capturer: screencastCapturer)
|
conferenceCall.requestVideo(capturer: screencastCapturer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1321,7 +1302,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
}
|
}
|
||||||
self.isScreencastActive = false
|
self.isScreencastActive = false
|
||||||
self.ongoingContext?.disableVideo()
|
self.ongoingContext?.disableVideo()
|
||||||
self.conferenceCall?.disableVideo()
|
self.conferenceCallImpl?.disableVideo()
|
||||||
if reset {
|
if reset {
|
||||||
self.resetScreencastContext()
|
self.resetScreencastContext()
|
||||||
}
|
}
|
||||||
@ -1332,6 +1313,25 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
self.videoCapturer?.setIsVideoEnabled(!isPaused)
|
self.videoCapturer?.setIsVideoEnabled(!isPaused)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func upgradeToConference(completion: @escaping (PresentationGroupCall) -> Void) -> Disposable {
|
||||||
|
if let conferenceCall = self.conferenceCall {
|
||||||
|
completion(conferenceCall)
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = self.upgradedToConferenceCompletions.add(completion)
|
||||||
|
self.callSessionManager.createConferenceIfNecessary(internalId: self.internalId)
|
||||||
|
|
||||||
|
return ActionDisposable { [weak self] in
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.upgradedToConferenceCompletions.remove(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func requestAddToConference(peerId: EnginePeer.Id) -> Disposable {
|
public func requestAddToConference(peerId: EnginePeer.Id) -> Disposable {
|
||||||
var conferenceCall: (conference: GroupCallReference, encryptionKey: Data)?
|
var conferenceCall: (conference: GroupCallReference, encryptionKey: Data)?
|
||||||
if let sessionState = self.sessionState {
|
if let sessionState = self.sessionState {
|
||||||
@ -1388,7 +1388,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
if isIncoming {
|
if isIncoming {
|
||||||
if let ongoingContext = self.ongoingContext {
|
if let ongoingContext = self.ongoingContext {
|
||||||
return ongoingContext.video(isIncoming: isIncoming)
|
return ongoingContext.video(isIncoming: isIncoming)
|
||||||
} else if let conferenceCall = self.conferenceCall, let remoteVideoEndpointId = self.remoteVideoEndpointId {
|
} else if let conferenceCall = self.conferenceCallImpl, let remoteVideoEndpointId = self.remoteVideoEndpointId {
|
||||||
return conferenceCall.video(endpointId: remoteVideoEndpointId)
|
return conferenceCall.video(endpointId: remoteVideoEndpointId)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
@ -1400,10 +1400,6 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func createConferenceIfPossible() {
|
|
||||||
self.callSessionManager.createConferenceIfNecessary(internalId: self.internalId)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) {
|
public func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) {
|
||||||
if self.videoCapturer == nil {
|
if self.videoCapturer == nil {
|
||||||
let videoCapturer = OngoingCallVideoCapturer()
|
let videoCapturer = OngoingCallVideoCapturer()
|
||||||
|
@ -1887,6 +1887,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
genericCallContext.setRequestedVideoChannels(self.suspendVideoChannelRequests ? [] : self.requestedVideoChannels)
|
genericCallContext.setRequestedVideoChannels(self.suspendVideoChannelRequests ? [] : self.requestedVideoChannels)
|
||||||
self.connectPendingVideoSubscribers()
|
self.connectPendingVideoSubscribers()
|
||||||
|
|
||||||
|
if let videoCapturer = self.videoCapturer {
|
||||||
|
genericCallContext.requestVideo(videoCapturer)
|
||||||
|
}
|
||||||
|
|
||||||
if case let .call(callContext) = genericCallContext {
|
if case let .call(callContext) = genericCallContext {
|
||||||
var lastTimestamp: Double?
|
var lastTimestamp: Double?
|
||||||
self.hasActiveIncomingDataDisposable?.dispose()
|
self.hasActiveIncomingDataDisposable?.dispose()
|
||||||
@ -2336,6 +2340,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
peerView = .single(nil)
|
peerView = .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.updateLocalVideoState()
|
||||||
|
|
||||||
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||||
participantsContext.state,
|
participantsContext.state,
|
||||||
participantsContext.activeSpeakers,
|
participantsContext.activeSpeakers,
|
||||||
|
@ -191,6 +191,7 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
private let pinchContainerNode: PinchSourceContainerNode
|
private let pinchContainerNode: PinchSourceContainerNode
|
||||||
private let extractedContainerView: ContextExtractedContentContainingView
|
private let extractedContainerView: ContextExtractedContentContainingView
|
||||||
private var videoSource: AdaptedCallVideoSource?
|
private var videoSource: AdaptedCallVideoSource?
|
||||||
|
private var videoPlaceholder: VideoSource.Output?
|
||||||
private var videoDisposable: Disposable?
|
private var videoDisposable: Disposable?
|
||||||
private var videoBackgroundLayer: SimpleLayer?
|
private var videoBackgroundLayer: SimpleLayer?
|
||||||
private var videoLayer: PrivateCallVideoLayer?
|
private var videoLayer: PrivateCallVideoLayer?
|
||||||
@ -263,6 +264,11 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updatePlaceholder(placeholder: VideoSource.Output) {
|
||||||
|
self.videoPlaceholder = placeholder
|
||||||
|
self.componentState?.updated(transition: .immediate, isLocal: true)
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: VideoChatParticipantVideoComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: VideoChatParticipantVideoComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
defer {
|
defer {
|
||||||
@ -456,6 +462,46 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
videoBackgroundLayer.isHidden = true
|
videoBackgroundLayer.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let videoUpdated: () -> Void = { [weak self] in
|
||||||
|
guard let self, let videoSource = self.videoSource, let videoLayer = self.videoLayer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoOutput = videoSource.currentOutput
|
||||||
|
var isPlaceholder = false
|
||||||
|
if videoOutput == nil {
|
||||||
|
isPlaceholder = true
|
||||||
|
videoOutput = self.videoPlaceholder
|
||||||
|
} else {
|
||||||
|
self.videoPlaceholder = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
videoLayer.video = videoOutput
|
||||||
|
|
||||||
|
if let videoOutput {
|
||||||
|
let videoSpec = VideoSpec(resolution: videoOutput.resolution, rotationAngle: videoOutput.rotationAngle, followsDeviceOrientation: videoOutput.followsDeviceOrientation)
|
||||||
|
if self.videoSpec != videoSpec || self.awaitingFirstVideoFrameForUnpause {
|
||||||
|
self.awaitingFirstVideoFrameForUnpause = false
|
||||||
|
|
||||||
|
self.videoSpec = videoSpec
|
||||||
|
if !self.isUpdating {
|
||||||
|
var transition: ComponentTransition = .immediate
|
||||||
|
if !isPlaceholder {
|
||||||
|
transition = transition.withUserData(AnimationHint(kind: .videoAvailabilityChanged))
|
||||||
|
}
|
||||||
|
self.componentState?.updated(transition: transition, isLocal: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.videoSpec != nil {
|
||||||
|
self.videoSpec = nil
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.componentState?.updated(transition: .immediate, isLocal: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let videoLayer: PrivateCallVideoLayer
|
let videoLayer: PrivateCallVideoLayer
|
||||||
if let current = self.videoLayer {
|
if let current = self.videoLayer {
|
||||||
videoLayer = current
|
videoLayer = current
|
||||||
@ -473,36 +519,16 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
self.videoSource = videoSource
|
self.videoSource = videoSource
|
||||||
|
|
||||||
self.videoDisposable?.dispose()
|
self.videoDisposable?.dispose()
|
||||||
self.videoDisposable = videoSource.addOnUpdated { [weak self] in
|
self.videoDisposable = videoSource.addOnUpdated {
|
||||||
guard let self, let videoSource = self.videoSource, let videoLayer = self.videoLayer else {
|
videoUpdated()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let videoOutput = videoSource.currentOutput
|
|
||||||
videoLayer.video = videoOutput
|
|
||||||
|
|
||||||
if let videoOutput {
|
|
||||||
let videoSpec = VideoSpec(resolution: videoOutput.resolution, rotationAngle: videoOutput.rotationAngle, followsDeviceOrientation: videoOutput.followsDeviceOrientation)
|
|
||||||
if self.videoSpec != videoSpec || self.awaitingFirstVideoFrameForUnpause {
|
|
||||||
self.awaitingFirstVideoFrameForUnpause = false
|
|
||||||
|
|
||||||
self.videoSpec = videoSpec
|
|
||||||
if !self.isUpdating {
|
|
||||||
self.componentState?.updated(transition: ComponentTransition.immediate.withUserData(AnimationHint(kind: .videoAvailabilityChanged)), isLocal: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if self.videoSpec != nil {
|
|
||||||
self.videoSpec = nil
|
|
||||||
if !self.isUpdating {
|
|
||||||
self.componentState?.updated(transition: .immediate, isLocal: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let _ = self.videoPlaceholder, videoLayer.video == nil {
|
||||||
|
videoUpdated()
|
||||||
|
}
|
||||||
|
|
||||||
transition.setFrame(layer: videoBackgroundLayer, frame: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setFrame(layer: videoBackgroundLayer, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
|
|
||||||
if let videoSpec = self.videoSpec {
|
if let videoSpec = self.videoSpec {
|
||||||
|
@ -11,6 +11,7 @@ import MultilineTextComponent
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import PeerListItemComponent
|
import PeerListItemComponent
|
||||||
import ContextUI
|
import ContextUI
|
||||||
|
import CallScreen
|
||||||
|
|
||||||
final class VideoChatParticipantsComponent: Component {
|
final class VideoChatParticipantsComponent: Component {
|
||||||
struct Layout: Equatable {
|
struct Layout: Equatable {
|
||||||
@ -1616,6 +1617,27 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func itemFrame(peerId: EnginePeer.Id, isPresentation: Bool) -> CGRect? {
|
||||||
|
for (key, itemView) in self.gridItemViews {
|
||||||
|
if key.id == peerId && key.isPresentation == isPresentation {
|
||||||
|
if let itemComponentView = itemView.view.view {
|
||||||
|
return itemComponentView.convert(itemComponentView.bounds, to: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateItemPlaceholder(peerId: EnginePeer.Id, isPresentation: Bool, placeholder: VideoSource.Output) {
|
||||||
|
for (key, itemView) in self.gridItemViews {
|
||||||
|
if key.id == peerId && key.isPresentation == isPresentation {
|
||||||
|
if let itemComponentView = itemView.view.view as? VideoChatParticipantVideoComponent.View {
|
||||||
|
itemComponentView.updatePlaceholder(placeholder: placeholder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: VideoChatParticipantsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: VideoChatParticipantsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
defer {
|
defer {
|
||||||
@ -1854,7 +1876,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
return UIColor(white: 1.0, alpha: 1.0)
|
return UIColor(white: 1.0, alpha: 1.0)
|
||||||
} else {
|
} else {
|
||||||
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||||
let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
|
let value: CGFloat = 1.0 - Display.bezierPoint(0.42, 0.0, 0.58, 1.0, step)
|
||||||
return UIColor(white: 0.0, alpha: baseGradientAlpha * value)
|
return UIColor(white: 0.0, alpha: baseGradientAlpha * value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,8 @@ final class VideoChatScreenComponent: Component {
|
|||||||
var focusedSpeakerAutoSwitchDeadline: Double = 0.0
|
var focusedSpeakerAutoSwitchDeadline: Double = 0.0
|
||||||
var isTwoColumnSidebarHidden: Bool = false
|
var isTwoColumnSidebarHidden: Bool = false
|
||||||
|
|
||||||
|
var isAnimatedOutFromPrivateCall: Bool = false
|
||||||
|
|
||||||
let inviteDisposable = MetaDisposable()
|
let inviteDisposable = MetaDisposable()
|
||||||
let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
|
let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
|
||||||
let updateAvatarDisposable = MetaDisposable()
|
let updateAvatarDisposable = MetaDisposable()
|
||||||
@ -164,6 +166,76 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.state?.updated(transition: .spring(duration: 0.5))
|
self.state?.updated(transition: .spring(duration: 0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateIn(sourceCallController: CallController) {
|
||||||
|
let sourceCallControllerView = sourceCallController.view
|
||||||
|
var isAnimationFinished = false
|
||||||
|
let animateOutData = sourceCallController.animateOutToGroupChat(completion: { [weak sourceCallControllerView] in
|
||||||
|
isAnimationFinished = true
|
||||||
|
sourceCallControllerView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
|
||||||
|
var expandedPeer: (id: EnginePeer.Id, isPresentation: Bool)?
|
||||||
|
if let animateOutData, animateOutData.incomingVideoLayer != nil {
|
||||||
|
if let members = self.members, let participant = members.participants.first(where: { $0.peer.id == animateOutData.incomingPeerId }) {
|
||||||
|
if let _ = participant.videoDescription {
|
||||||
|
expandedPeer = (participant.peer.id, false)
|
||||||
|
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isAnimatedOutFromPrivateCall = true
|
||||||
|
self.verticalPanState = nil
|
||||||
|
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
|
if !isAnimationFinished {
|
||||||
|
if let participantsView = self.participants.view {
|
||||||
|
self.containerView.insertSubview(sourceCallController.view, belowSubview: participantsView)
|
||||||
|
} else {
|
||||||
|
self.containerView.addSubview(sourceCallController.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let transition: ComponentTransition = .spring(duration: 0.4)
|
||||||
|
let alphaTransition: ComponentTransition = .easeInOut(duration: 0.25)
|
||||||
|
|
||||||
|
self.isAnimatedOutFromPrivateCall = false
|
||||||
|
self.expandedParticipantsVideoState = nil
|
||||||
|
self.state?.updated(transition: transition)
|
||||||
|
|
||||||
|
if let animateOutData, let expandedPeer, let incomingVideoLayer = animateOutData.incomingVideoLayer, let participantsView = self.participants.view as? VideoChatParticipantsComponent.View, let targetFrame = participantsView.itemFrame(peerId: expandedPeer.id, isPresentation: expandedPeer.isPresentation) {
|
||||||
|
if let incomingVideoPlaceholder = animateOutData.incomingVideoPlaceholder {
|
||||||
|
participantsView.updateItemPlaceholder(peerId: expandedPeer.id, isPresentation: expandedPeer.isPresentation, placeholder: incomingVideoPlaceholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
let incomingVideoLayerFrame = incomingVideoLayer.convert(incomingVideoLayer.frame, to: sourceCallControllerView?.layer)
|
||||||
|
|
||||||
|
let targetContainer = SimpleLayer()
|
||||||
|
targetContainer.masksToBounds = true
|
||||||
|
targetContainer.backgroundColor = UIColor.blue.cgColor
|
||||||
|
targetContainer.cornerRadius = 10.0
|
||||||
|
|
||||||
|
self.containerView.layer.insertSublayer(targetContainer, above: participantsView.layer)
|
||||||
|
|
||||||
|
targetContainer.frame = incomingVideoLayerFrame
|
||||||
|
|
||||||
|
targetContainer.addSublayer(incomingVideoLayer)
|
||||||
|
incomingVideoLayer.position = CGRect(origin: CGPoint(), size: incomingVideoLayerFrame.size).center
|
||||||
|
let sourceFitScale = max(incomingVideoLayerFrame.width / incomingVideoLayerFrame.width, incomingVideoLayerFrame.height / incomingVideoLayerFrame.height)
|
||||||
|
incomingVideoLayer.transform = CATransform3DMakeScale(sourceFitScale, sourceFitScale, 1.0)
|
||||||
|
|
||||||
|
let targetFrame = participantsView.convert(targetFrame, to: self)
|
||||||
|
let targetFitScale = min(incomingVideoLayerFrame.width / targetFrame.width, incomingVideoLayerFrame.height / targetFrame.height)
|
||||||
|
|
||||||
|
transition.setFrame(layer: targetContainer, frame: targetFrame, completion: { [weak targetContainer] _ in
|
||||||
|
targetContainer?.removeFromSuperlayer()
|
||||||
|
})
|
||||||
|
transition.setTransform(layer: incomingVideoLayer, transform: CATransform3DMakeScale(targetFitScale, targetFitScale, 1.0))
|
||||||
|
alphaTransition.setAlpha(layer: targetContainer, alpha: 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func animateOut(completion: @escaping () -> Void) {
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
self.verticalPanState = PanState(fraction: 1.0, scrollView: nil)
|
self.verticalPanState = PanState(fraction: 1.0, scrollView: nil)
|
||||||
self.completionOnPanGestureApply = completion
|
self.completionOnPanGestureApply = completion
|
||||||
@ -1027,30 +1099,32 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||||
})
|
})
|
||||||
|
|
||||||
self.memberEventsDisposable = (component.call.memberEvents
|
if component.call.peerId != nil {
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] event in
|
self.memberEventsDisposable = (component.call.memberEvents
|
||||||
guard let self, let members = self.members, let component = self.component, let environment = self.environment else {
|
|> deliverOnMainQueue).start(next: { [weak self] event in
|
||||||
return
|
guard let self, let members = self.members, let component = self.component, let environment = self.environment else {
|
||||||
}
|
return
|
||||||
if event.joined {
|
|
||||||
var displayEvent = false
|
|
||||||
if case let .channel(channel) = self.peer, case .broadcast = channel.info {
|
|
||||||
displayEvent = false
|
|
||||||
}
|
}
|
||||||
if members.totalCount < 40 {
|
if event.joined {
|
||||||
displayEvent = true
|
var displayEvent = false
|
||||||
} else if event.peer.isVerified {
|
if case let .channel(channel) = self.peer, case .broadcast = channel.info {
|
||||||
displayEvent = true
|
displayEvent = false
|
||||||
} else if event.isContact || event.isInChatList {
|
}
|
||||||
displayEvent = true
|
if members.totalCount < 40 {
|
||||||
|
displayEvent = true
|
||||||
|
} else if event.peer.isVerified {
|
||||||
|
displayEvent = true
|
||||||
|
} else if event.isContact || event.isInChatList {
|
||||||
|
displayEvent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if displayEvent {
|
||||||
|
let text = environment.strings.VoiceChat_PeerJoinedText(event.peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
||||||
|
self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: event.peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
if displayEvent {
|
}
|
||||||
let text = environment.strings.VoiceChat_PeerJoinedText(event.peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string
|
|
||||||
self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: event.peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.isPresentedValue.set(environment.isVisible)
|
self.isPresentedValue.set(environment.isVisible)
|
||||||
@ -1210,6 +1284,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.containerView.addSubview(navigationLeftButtonView)
|
self.containerView.addSubview(navigationLeftButtonView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: navigationLeftButtonView, frame: navigationLeftButtonFrame)
|
transition.setFrame(view: navigationLeftButtonView, frame: navigationLeftButtonFrame)
|
||||||
|
alphaTransition.setAlpha(view: navigationLeftButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize)
|
let navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize)
|
||||||
@ -1218,6 +1293,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.containerView.addSubview(navigationRightButtonView)
|
self.containerView.addSubview(navigationRightButtonView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: navigationRightButtonView, frame: navigationRightButtonFrame)
|
transition.setFrame(view: navigationRightButtonView, frame: navigationRightButtonFrame)
|
||||||
|
alphaTransition.setAlpha(view: navigationRightButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isTwoColumnLayout {
|
if isTwoColumnLayout {
|
||||||
@ -1300,10 +1376,11 @@ final class VideoChatScreenComponent: Component {
|
|||||||
maxTitleWidth -= 110.0
|
maxTitleWidth -= 110.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(VideoChatTitleComponent(
|
component: AnyComponent(VideoChatTitleComponent(
|
||||||
title: self.callState?.title ?? self.peer?.debugDisplayTitle ?? " ",
|
title: self.callState?.title ?? self.peer?.debugDisplayTitle ?? "Group Call",
|
||||||
status: idleTitleStatusText,
|
status: idleTitleStatusText,
|
||||||
isRecording: self.callState?.recordingStartTimestamp != nil,
|
isRecording: self.callState?.recordingStartTimestamp != nil,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
@ -1350,6 +1427,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
self.containerView.addSubview(titleView)
|
self.containerView.addSubview(titleView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: titleView, frame: titleFrame)
|
transition.setFrame(view: titleView, frame: titleFrame)
|
||||||
|
alphaTransition.setAlpha(view: titleView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let areButtonsCollapsed: Bool
|
let areButtonsCollapsed: Bool
|
||||||
@ -1411,6 +1489,10 @@ final class VideoChatScreenComponent: Component {
|
|||||||
let actionMicrophoneButtonSpacing = min(effectiveMaxActionMicrophoneButtonSpacing, floor(remainingButtonsSpace * 0.5))
|
let actionMicrophoneButtonSpacing = min(effectiveMaxActionMicrophoneButtonSpacing, floor(remainingButtonsSpace * 0.5))
|
||||||
|
|
||||||
var collapsedMicrophoneButtonFrame: CGRect = CGRect(origin: CGPoint(x: floor((availableSize.width - collapsedMicrophoneButtonDiameter) * 0.5), y: availableSize.height - 48.0 - environment.safeInsets.bottom - collapsedMicrophoneButtonDiameter), size: CGSize(width: collapsedMicrophoneButtonDiameter, height: collapsedMicrophoneButtonDiameter))
|
var collapsedMicrophoneButtonFrame: CGRect = CGRect(origin: CGPoint(x: floor((availableSize.width - collapsedMicrophoneButtonDiameter) * 0.5), y: availableSize.height - 48.0 - environment.safeInsets.bottom - collapsedMicrophoneButtonDiameter), size: CGSize(width: collapsedMicrophoneButtonDiameter, height: collapsedMicrophoneButtonDiameter))
|
||||||
|
if self.isAnimatedOutFromPrivateCall {
|
||||||
|
collapsedMicrophoneButtonFrame.origin.y = availableSize.height + 48.0
|
||||||
|
}
|
||||||
|
|
||||||
var expandedMicrophoneButtonFrame: CGRect = CGRect(origin: CGPoint(x: floor((availableSize.width - expandedMicrophoneButtonDiameter) * 0.5), y: availableSize.height - environment.safeInsets.bottom - expandedMicrophoneButtonDiameter - 12.0), size: CGSize(width: expandedMicrophoneButtonDiameter, height: expandedMicrophoneButtonDiameter))
|
var expandedMicrophoneButtonFrame: CGRect = CGRect(origin: CGPoint(x: floor((availableSize.width - expandedMicrophoneButtonDiameter) * 0.5), y: availableSize.height - environment.safeInsets.bottom - expandedMicrophoneButtonDiameter - 12.0), size: CGSize(width: expandedMicrophoneButtonDiameter, height: expandedMicrophoneButtonDiameter))
|
||||||
|
|
||||||
var isMainColumnHidden = false
|
var isMainColumnHidden = false
|
||||||
@ -1617,6 +1699,9 @@ final class VideoChatScreenComponent: Component {
|
|||||||
if let callState = self.callState, callState.scheduleTimestamp != nil {
|
if let callState = self.callState, callState.scheduleTimestamp != nil {
|
||||||
participantsAlpha = 0.0
|
participantsAlpha = 0.0
|
||||||
}
|
}
|
||||||
|
if self.isAnimatedOutFromPrivateCall {
|
||||||
|
participantsAlpha = 0.0
|
||||||
|
}
|
||||||
alphaTransition.setAlpha(view: participantsView, alpha: participantsAlpha)
|
alphaTransition.setAlpha(view: participantsView, alpha: participantsAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1919,12 +2004,16 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo
|
|||||||
private var isAnimatingDismiss: Bool = false
|
private var isAnimatingDismiss: Bool = false
|
||||||
|
|
||||||
private var idleTimerExtensionDisposable: Disposable?
|
private var idleTimerExtensionDisposable: Disposable?
|
||||||
|
|
||||||
|
private var sourceCallController: CallController?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
initialData: InitialData,
|
initialData: InitialData,
|
||||||
call: PresentationGroupCall
|
call: PresentationGroupCall,
|
||||||
|
sourceCallController: CallController?
|
||||||
) {
|
) {
|
||||||
self.call = call
|
self.call = call
|
||||||
|
self.sourceCallController = sourceCallController
|
||||||
|
|
||||||
let theme = customizeDefaultDarkPresentationTheme(
|
let theme = customizeDefaultDarkPresentationTheme(
|
||||||
theme: defaultDarkPresentationTheme,
|
theme: defaultDarkPresentationTheme,
|
||||||
@ -1964,7 +2053,12 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo
|
|||||||
self.isDismissed = false
|
self.isDismissed = false
|
||||||
|
|
||||||
if let componentView = self.node.hostView.componentView as? VideoChatScreenComponent.View {
|
if let componentView = self.node.hostView.componentView as? VideoChatScreenComponent.View {
|
||||||
componentView.animateIn()
|
if let sourceCallController = self.sourceCallController {
|
||||||
|
self.sourceCallController = nil
|
||||||
|
componentView.animateIn(sourceCallController: sourceCallController)
|
||||||
|
} else {
|
||||||
|
componentView.animateIn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7148,12 +7148,6 @@ public func makeVoiceChatControllerInitialData(sharedContext: SharedAccountConte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeVoiceChatController(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall, initialData: Any) -> VoiceChatController {
|
public func makeVoiceChatController(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall, initialData: Any, sourceCallController: CallController?) -> VoiceChatController {
|
||||||
let useV2 = shouldUseV2VideoChatImpl(context: accountContext)
|
return VideoChatScreenV2Impl(initialData: initialData as! VideoChatScreenV2Impl.InitialData, call: call, sourceCallController: sourceCallController)
|
||||||
|
|
||||||
if useV2 {
|
|
||||||
return VideoChatScreenV2Impl(initialData: initialData as! VideoChatScreenV2Impl.InitialData, call: call)
|
|
||||||
} else {
|
|
||||||
return VoiceChatControllerImpl(sharedContext: sharedContext, accountContext: accountContext, call: call)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ swift_library(
|
|||||||
"//submodules/Utils/RangeSet:RangeSet",
|
"//submodules/Utils/RangeSet:RangeSet",
|
||||||
"//submodules/Utils/DarwinDirStat",
|
"//submodules/Utils/DarwinDirStat",
|
||||||
"//submodules/Emoji",
|
"//submodules/Emoji",
|
||||||
|
"//submodules/TelegramCore/FlatSerialization",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
16
submodules/TelegramCore/FlatBuffers/BUILD
Normal file
16
submodules/TelegramCore/FlatBuffers/BUILD
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "FlatBuffers",
|
||||||
|
module_name = "FlatBuffers",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
542
submodules/TelegramCore/FlatBuffers/Sources/ByteBuffer.swift
Normal file
542
submodules/TelegramCore/FlatBuffers/Sources/ByteBuffer.swift
Normal file
@ -0,0 +1,542 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// `ByteBuffer` is the interface that stores the data for a `Flatbuffers` object
|
||||||
|
/// it allows users to write and read data directly from memory thus the use of its
|
||||||
|
/// functions should be used
|
||||||
|
@frozen
|
||||||
|
public struct ByteBuffer {
|
||||||
|
|
||||||
|
/// Storage is a container that would hold the memory pointer to solve the issue of
|
||||||
|
/// deallocating the memory that was held by (memory: UnsafeMutableRawPointer)
|
||||||
|
@usableFromInline
|
||||||
|
final class Storage {
|
||||||
|
// This storage doesn't own the memory, therefore, we won't deallocate on deinit.
|
||||||
|
private let unowned: Bool
|
||||||
|
/// pointer to the start of the buffer object in memory
|
||||||
|
var memory: UnsafeMutableRawPointer
|
||||||
|
/// Capacity of UInt8 the buffer can hold
|
||||||
|
var capacity: Int
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
init(count: Int, alignment: Int) {
|
||||||
|
memory = UnsafeMutableRawPointer.allocate(
|
||||||
|
byteCount: count,
|
||||||
|
alignment: alignment)
|
||||||
|
capacity = count
|
||||||
|
unowned = false
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
init(memory: UnsafeMutableRawPointer, capacity: Int, unowned: Bool) {
|
||||||
|
self.memory = memory
|
||||||
|
self.capacity = capacity
|
||||||
|
self.unowned = unowned
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
if !unowned {
|
||||||
|
memory.deallocate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
func copy(from ptr: UnsafeRawPointer, count: Int) {
|
||||||
|
assert(
|
||||||
|
!unowned,
|
||||||
|
"copy should NOT be called on a buffer that is built by assumingMemoryBound")
|
||||||
|
memory.copyMemory(from: ptr, byteCount: count)
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
func initialize(for size: Int) {
|
||||||
|
assert(
|
||||||
|
!unowned,
|
||||||
|
"initalize should NOT be called on a buffer that is built by assumingMemoryBound")
|
||||||
|
memset(memory, 0, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reallocates the buffer incase the object to be written doesnt fit in the current buffer
|
||||||
|
/// - Parameter size: Size of the current object
|
||||||
|
@usableFromInline
|
||||||
|
func reallocate(_ size: Int, writerSize: Int, alignment: Int) {
|
||||||
|
let currentWritingIndex = capacity &- writerSize
|
||||||
|
while capacity <= writerSize &+ size {
|
||||||
|
capacity = capacity << 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// solution take from Apple-NIO
|
||||||
|
capacity = capacity.convertToPowerofTwo
|
||||||
|
|
||||||
|
let newData = UnsafeMutableRawPointer.allocate(
|
||||||
|
byteCount: capacity,
|
||||||
|
alignment: alignment)
|
||||||
|
memset(newData, 0, capacity &- writerSize)
|
||||||
|
memcpy(
|
||||||
|
newData.advanced(by: capacity &- writerSize),
|
||||||
|
memory.advanced(by: currentWritingIndex),
|
||||||
|
writerSize)
|
||||||
|
memory.deallocate()
|
||||||
|
memory = newData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline var _storage: Storage
|
||||||
|
|
||||||
|
/// The size of the elements written to the buffer + their paddings
|
||||||
|
private var _writerSize: Int = 0
|
||||||
|
/// Alignment of the current memory being written to the buffer
|
||||||
|
var alignment = 1
|
||||||
|
/// Current Index which is being used to write to the buffer, it is written from the end to the start of the buffer
|
||||||
|
var writerIndex: Int { _storage.capacity &- _writerSize }
|
||||||
|
|
||||||
|
/// Reader is the position of the current Writer Index (capacity - size)
|
||||||
|
public var reader: Int { writerIndex }
|
||||||
|
/// Current size of the buffer
|
||||||
|
public var size: UOffset { UOffset(_writerSize) }
|
||||||
|
/// Public Pointer to the buffer object in memory. This should NOT be modified for any reason
|
||||||
|
public var memory: UnsafeMutableRawPointer { _storage.memory }
|
||||||
|
/// Current capacity for the buffer
|
||||||
|
public var capacity: Int { _storage.capacity }
|
||||||
|
/// Crash if the trying to read an unaligned buffer instead of allowing users to read them.
|
||||||
|
public let allowReadingUnalignedBuffers: Bool
|
||||||
|
|
||||||
|
/// Constructor that creates a Flatbuffer object from a UInt8
|
||||||
|
/// - Parameter
|
||||||
|
/// - bytes: Array of UInt8
|
||||||
|
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
|
||||||
|
public init(
|
||||||
|
bytes: [UInt8],
|
||||||
|
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
|
||||||
|
{
|
||||||
|
var b = bytes
|
||||||
|
_storage = Storage(count: bytes.count, alignment: alignment)
|
||||||
|
_writerSize = _storage.capacity
|
||||||
|
allowReadingUnalignedBuffers = allowUnalignedBuffers
|
||||||
|
b.withUnsafeMutableBytes { bufferPointer in
|
||||||
|
_storage.copy(from: bufferPointer.baseAddress!, count: bytes.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !os(WASI)
|
||||||
|
/// Constructor that creates a Flatbuffer from the Swift Data type object
|
||||||
|
/// - Parameter
|
||||||
|
/// - data: Swift data Object
|
||||||
|
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
|
||||||
|
public init(
|
||||||
|
data: Data,
|
||||||
|
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
|
||||||
|
{
|
||||||
|
var b = data
|
||||||
|
_storage = Storage(count: data.count, alignment: alignment)
|
||||||
|
_writerSize = _storage.capacity
|
||||||
|
allowReadingUnalignedBuffers = allowUnalignedBuffers
|
||||||
|
b.withUnsafeMutableBytes { bufferPointer in
|
||||||
|
_storage.copy(from: bufferPointer.baseAddress!, count: data.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Constructor that creates a Flatbuffer instance with a size
|
||||||
|
/// - Parameter:
|
||||||
|
/// - size: Length of the buffer
|
||||||
|
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
|
||||||
|
init(initialSize size: Int) {
|
||||||
|
let size = size.convertToPowerofTwo
|
||||||
|
_storage = Storage(count: size, alignment: alignment)
|
||||||
|
_storage.initialize(for: size)
|
||||||
|
allowReadingUnalignedBuffers = false
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.0) && !os(WASI)
|
||||||
|
/// Constructor that creates a Flatbuffer object from a ContiguousBytes
|
||||||
|
/// - Parameters:
|
||||||
|
/// - contiguousBytes: Binary stripe to use as the buffer
|
||||||
|
/// - count: amount of readable bytes
|
||||||
|
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
|
||||||
|
public init<Bytes: ContiguousBytes>(
|
||||||
|
contiguousBytes: Bytes,
|
||||||
|
count: Int,
|
||||||
|
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
|
||||||
|
{
|
||||||
|
_storage = Storage(count: count, alignment: alignment)
|
||||||
|
_writerSize = _storage.capacity
|
||||||
|
allowReadingUnalignedBuffers = allowUnalignedBuffers
|
||||||
|
contiguousBytes.withUnsafeBytes { buf in
|
||||||
|
_storage.copy(from: buf.baseAddress!, count: buf.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Constructor that creates a Flatbuffer from unsafe memory region without copying
|
||||||
|
/// - Parameter:
|
||||||
|
/// - assumingMemoryBound: The unsafe memory region
|
||||||
|
/// - capacity: The size of the given memory region
|
||||||
|
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
|
||||||
|
public init(
|
||||||
|
assumingMemoryBound memory: UnsafeMutableRawPointer,
|
||||||
|
capacity: Int,
|
||||||
|
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
|
||||||
|
{
|
||||||
|
_storage = Storage(memory: memory, capacity: capacity, unowned: true)
|
||||||
|
_writerSize = capacity
|
||||||
|
allowReadingUnalignedBuffers = allowUnalignedBuffers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a copy of the buffer that's being built by calling sizedBuffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - memory: Current memory of the buffer
|
||||||
|
/// - count: count of bytes
|
||||||
|
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
|
||||||
|
init(
|
||||||
|
memory: UnsafeMutableRawPointer,
|
||||||
|
count: Int,
|
||||||
|
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
|
||||||
|
{
|
||||||
|
_storage = Storage(count: count, alignment: alignment)
|
||||||
|
_storage.copy(from: memory, count: count)
|
||||||
|
_writerSize = _storage.capacity
|
||||||
|
allowReadingUnalignedBuffers = allowUnalignedBuffers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a copy of the existing flatbuffer, by copying it to a different memory.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - memory: Current memory of the buffer
|
||||||
|
/// - count: count of bytes
|
||||||
|
/// - removeBytes: Removes a number of bytes from the current size
|
||||||
|
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
|
||||||
|
init(
|
||||||
|
memory: UnsafeMutableRawPointer,
|
||||||
|
count: Int,
|
||||||
|
removing removeBytes: Int,
|
||||||
|
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
|
||||||
|
{
|
||||||
|
_storage = Storage(count: count, alignment: alignment)
|
||||||
|
_storage.copy(from: memory, count: count)
|
||||||
|
_writerSize = removeBytes
|
||||||
|
allowReadingUnalignedBuffers = allowUnalignedBuffers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills the buffer with padding by adding to the writersize
|
||||||
|
/// - Parameter padding: Amount of padding between two to be serialized objects
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating func fill(padding: Int) {
|
||||||
|
assert(padding >= 0, "Fill should be larger than or equal to zero")
|
||||||
|
ensureSpace(size: padding)
|
||||||
|
_writerSize = _writerSize &+ (MemoryLayout<UInt8>.size &* padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an array of type Scalar to the buffer memory
|
||||||
|
/// - Parameter elements: An array of Scalars
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating func push<T: Scalar>(elements: [T]) {
|
||||||
|
elements.withUnsafeBytes { ptr in
|
||||||
|
ensureSpace(size: ptr.count)
|
||||||
|
memcpy(
|
||||||
|
_storage.memory.advanced(by: writerIndex &- ptr.count),
|
||||||
|
ptr.baseAddress!,
|
||||||
|
ptr.count)
|
||||||
|
_writerSize = _writerSize &+ ptr.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an array of type Scalar to the buffer memory
|
||||||
|
/// - Parameter elements: An array of Scalars
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating func push<T: NativeStruct>(elements: [T]) {
|
||||||
|
elements.withUnsafeBytes { ptr in
|
||||||
|
ensureSpace(size: ptr.count)
|
||||||
|
memcpy(
|
||||||
|
_storage.memory.advanced(by: writerIndex &- ptr.count),
|
||||||
|
ptr.baseAddress!,
|
||||||
|
ptr.count)
|
||||||
|
_writerSize = _writerSize &+ ptr.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a `ContiguousBytes` to buffer memory
|
||||||
|
/// - Parameter value: bytes to copy
|
||||||
|
#if swift(>=5.0) && !os(WASI)
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating func push(bytes: ContiguousBytes) {
|
||||||
|
bytes.withUnsafeBytes { ptr in
|
||||||
|
ensureSpace(size: ptr.count)
|
||||||
|
memcpy(
|
||||||
|
_storage.memory.advanced(by: writerIndex &- ptr.count),
|
||||||
|
ptr.baseAddress!,
|
||||||
|
ptr.count)
|
||||||
|
_writerSize = _writerSize &+ ptr.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Adds an object of type NativeStruct into the buffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - value: Object that will be written to the buffer
|
||||||
|
/// - size: size to subtract from the WriterIndex
|
||||||
|
@usableFromInline
|
||||||
|
@inline(__always)
|
||||||
|
mutating func push<T: NativeStruct>(struct value: T, size: Int) {
|
||||||
|
ensureSpace(size: size)
|
||||||
|
withUnsafePointer(to: value) {
|
||||||
|
memcpy(
|
||||||
|
_storage.memory.advanced(by: writerIndex &- size),
|
||||||
|
$0,
|
||||||
|
size)
|
||||||
|
_writerSize = _writerSize &+ size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an object of type Scalar into the buffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - value: Object that will be written to the buffer
|
||||||
|
/// - len: Offset to subtract from the WriterIndex
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating func push<T: Scalar>(value: T, len: Int) {
|
||||||
|
ensureSpace(size: len)
|
||||||
|
withUnsafePointer(to: value) {
|
||||||
|
memcpy(
|
||||||
|
_storage.memory.advanced(by: writerIndex &- len),
|
||||||
|
$0,
|
||||||
|
len)
|
||||||
|
_writerSize = _writerSize &+ len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a string to the buffer using swift.utf8 object
|
||||||
|
/// - Parameter str: String that will be added to the buffer
|
||||||
|
/// - Parameter len: length of the string
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating func push(string str: String, len: Int) {
|
||||||
|
ensureSpace(size: len)
|
||||||
|
if str.utf8
|
||||||
|
.withContiguousStorageIfAvailable({ self.push(bytes: $0, len: len) }) !=
|
||||||
|
nil
|
||||||
|
{
|
||||||
|
} else {
|
||||||
|
let utf8View = str.utf8
|
||||||
|
for c in utf8View.reversed() {
|
||||||
|
push(value: c, len: 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a string to Bytebuffer using UTF8View
|
||||||
|
/// - Parameters:
|
||||||
|
/// - bytes: Pointer to the view
|
||||||
|
/// - len: Size of string
|
||||||
|
@usableFromInline
|
||||||
|
@inline(__always)
|
||||||
|
mutating func push(
|
||||||
|
bytes: UnsafeBufferPointer<String.UTF8View.Element>,
|
||||||
|
len: Int) -> Bool
|
||||||
|
{
|
||||||
|
memcpy(
|
||||||
|
_storage.memory.advanced(by: writerIndex &- len),
|
||||||
|
bytes.baseAddress!,
|
||||||
|
len)
|
||||||
|
_writerSize = _writerSize &+ len
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write stores an object into the buffer directly or indirectly.
|
||||||
|
///
|
||||||
|
/// Direct: ignores the capacity of buffer which would mean we are referring to the direct point in memory
|
||||||
|
/// indirect: takes into respect the current capacity of the buffer (capacity - index), writing to the buffer from the end
|
||||||
|
/// - Parameters:
|
||||||
|
/// - value: Value that needs to be written to the buffer
|
||||||
|
/// - index: index to write to
|
||||||
|
/// - direct: Should take into consideration the capacity of the buffer
|
||||||
|
@inline(__always)
|
||||||
|
func write<T>(value: T, index: Int, direct: Bool = false) {
|
||||||
|
var index = index
|
||||||
|
if !direct {
|
||||||
|
index = _storage.capacity &- index
|
||||||
|
}
|
||||||
|
assert(index < _storage.capacity, "Write index is out of writing bound")
|
||||||
|
assert(index >= 0, "Writer index should be above zero")
|
||||||
|
withUnsafePointer(to: value) {
|
||||||
|
memcpy(
|
||||||
|
_storage.memory.advanced(by: index),
|
||||||
|
$0,
|
||||||
|
MemoryLayout<T>.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes sure that buffer has enouch space for each of the objects that will be written into it
|
||||||
|
/// - Parameter size: size of object
|
||||||
|
@discardableResult
|
||||||
|
@usableFromInline
|
||||||
|
@inline(__always)
|
||||||
|
mutating func ensureSpace(size: Int) -> Int {
|
||||||
|
if size &+ _writerSize > _storage.capacity {
|
||||||
|
_storage.reallocate(size, writerSize: _writerSize, alignment: alignment)
|
||||||
|
}
|
||||||
|
assert(size < FlatBufferMaxSize, "Buffer can't grow beyond 2 Gigabytes")
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// pops the written VTable if it's already written into the buffer
|
||||||
|
/// - Parameter size: size of the `VTable`
|
||||||
|
@usableFromInline
|
||||||
|
@inline(__always)
|
||||||
|
mutating func pop(_ size: Int) {
|
||||||
|
assert(
|
||||||
|
(_writerSize &- size) > 0,
|
||||||
|
"New size should NOT be a negative number")
|
||||||
|
memset(_storage.memory.advanced(by: writerIndex), 0, _writerSize &- size)
|
||||||
|
_writerSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the current size of the buffer
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func clearSize() {
|
||||||
|
_writerSize = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the current instance of the buffer, replacing it with new memory
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func clear() {
|
||||||
|
_writerSize = 0
|
||||||
|
alignment = 1
|
||||||
|
_storage.initialize(for: _storage.capacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads an object from the buffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - def: Type of the object
|
||||||
|
/// - position: the index of the object in the buffer
|
||||||
|
@inline(__always)
|
||||||
|
public func read<T>(def: T.Type, position: Int) -> T {
|
||||||
|
if allowReadingUnalignedBuffers {
|
||||||
|
return _storage.memory.advanced(by: position).loadUnaligned(as: T.self)
|
||||||
|
}
|
||||||
|
return _storage.memory.advanced(by: position).load(as: T.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a slice from the memory assuming a type of T
|
||||||
|
/// - Parameters:
|
||||||
|
/// - index: index of the object to be read from the buffer
|
||||||
|
/// - count: count of bytes in memory
|
||||||
|
@inline(__always)
|
||||||
|
public func readSlice<T>(
|
||||||
|
index: Int,
|
||||||
|
count: Int) -> [T]
|
||||||
|
{
|
||||||
|
assert(
|
||||||
|
index + count <= _storage.capacity,
|
||||||
|
"Reading out of bounds is illegal")
|
||||||
|
let start = _storage.memory.advanced(by: index)
|
||||||
|
.assumingMemoryBound(to: T.self)
|
||||||
|
let array = UnsafeBufferPointer(start: start, count: count)
|
||||||
|
return Array(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !os(WASI)
|
||||||
|
/// Reads a string from the buffer and encodes it to a swift string
|
||||||
|
/// - Parameters:
|
||||||
|
/// - index: index of the string in the buffer
|
||||||
|
/// - count: length of the string
|
||||||
|
/// - type: Encoding of the string
|
||||||
|
@inline(__always)
|
||||||
|
public func readString(
|
||||||
|
at index: Int,
|
||||||
|
count: Int,
|
||||||
|
type: String.Encoding = .utf8) -> String?
|
||||||
|
{
|
||||||
|
assert(
|
||||||
|
index + count <= _storage.capacity,
|
||||||
|
"Reading out of bounds is illegal")
|
||||||
|
let start = _storage.memory.advanced(by: index)
|
||||||
|
.assumingMemoryBound(to: UInt8.self)
|
||||||
|
let bufprt = UnsafeBufferPointer(start: start, count: count)
|
||||||
|
return String(bytes: Array(bufprt), encoding: type)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
/// Reads a string from the buffer and encodes it to a swift string
|
||||||
|
/// - Parameters:
|
||||||
|
/// - index: index of the string in the buffer
|
||||||
|
/// - count: length of the string
|
||||||
|
@inline(__always)
|
||||||
|
public func readString(
|
||||||
|
at index: Int,
|
||||||
|
count: Int) -> String?
|
||||||
|
{
|
||||||
|
assert(
|
||||||
|
index + count <= _storage.capacity,
|
||||||
|
"Reading out of bounds is illegal")
|
||||||
|
let start = _storage.memory.advanced(by: index)
|
||||||
|
.assumingMemoryBound(to: UInt8.self)
|
||||||
|
let bufprt = UnsafeBufferPointer(start: start, count: count)
|
||||||
|
return String(cString: bufprt.baseAddress!)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Creates a new Flatbuffer object that's duplicated from the current one
|
||||||
|
/// - Parameter removeBytes: the amount of bytes to remove from the current Size
|
||||||
|
@inline(__always)
|
||||||
|
public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer {
|
||||||
|
assert(removeBytes > 0, "Can NOT remove negative bytes")
|
||||||
|
assert(
|
||||||
|
removeBytes < _storage.capacity,
|
||||||
|
"Can NOT remove more bytes than the ones allocated")
|
||||||
|
return ByteBuffer(
|
||||||
|
memory: _storage.memory,
|
||||||
|
count: _storage.capacity,
|
||||||
|
removing: _writerSize &- removeBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the written bytes into the ``ByteBuffer``
|
||||||
|
public var underlyingBytes: [UInt8] {
|
||||||
|
let cp = capacity &- writerIndex
|
||||||
|
let start = memory.advanced(by: writerIndex)
|
||||||
|
.bindMemory(to: UInt8.self, capacity: cp)
|
||||||
|
|
||||||
|
let ptr = UnsafeBufferPointer<UInt8>(start: start, count: cp)
|
||||||
|
return Array(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SkipPrefix Skips the first 4 bytes in case one of the following
|
||||||
|
/// functions are called `getPrefixedSizeCheckedRoot` & `getPrefixedSizeRoot`
|
||||||
|
/// which allows us to skip the first 4 bytes instead of recreating the buffer
|
||||||
|
@discardableResult
|
||||||
|
@usableFromInline
|
||||||
|
@inline(__always)
|
||||||
|
mutating func skipPrefix() -> Int32 {
|
||||||
|
_writerSize = _writerSize &- MemoryLayout<Int32>.size
|
||||||
|
return read(def: Int32.self, position: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ByteBuffer: CustomDebugStringConvertible {
|
||||||
|
|
||||||
|
public var debugDescription: String {
|
||||||
|
"""
|
||||||
|
buffer located at: \(_storage.memory), with capacity of \(_storage.capacity)
|
||||||
|
{ writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \(
|
||||||
|
writerIndex) }
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
114
submodules/TelegramCore/FlatBuffers/Sources/Constants.swift
Normal file
114
submodules/TelegramCore/FlatBuffers/Sources/Constants.swift
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// A boolean to see if the system is littleEndian
|
||||||
|
let isLitteEndian: Bool = {
|
||||||
|
let number: UInt32 = 0x12345678
|
||||||
|
return number == number.littleEndian
|
||||||
|
}()
|
||||||
|
/// Constant for the file id length
|
||||||
|
let FileIdLength = 4
|
||||||
|
/// Type aliases
|
||||||
|
public typealias Byte = UInt8
|
||||||
|
public typealias UOffset = UInt32
|
||||||
|
public typealias SOffset = Int32
|
||||||
|
public typealias VOffset = UInt16
|
||||||
|
/// Maximum size for a buffer
|
||||||
|
public let FlatBufferMaxSize = UInt32
|
||||||
|
.max << ((MemoryLayout<SOffset>.size * 8 - 1) - 1)
|
||||||
|
|
||||||
|
/// Protocol that All Scalars should conform to
|
||||||
|
///
|
||||||
|
/// Scalar is used to conform all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer.
|
||||||
|
public protocol Scalar: Equatable {
|
||||||
|
associatedtype NumericValue
|
||||||
|
var convertedEndian: NumericValue { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Scalar where Self: Verifiable {}
|
||||||
|
|
||||||
|
extension Scalar where Self: FixedWidthInteger {
|
||||||
|
/// Converts the value from BigEndian to LittleEndian
|
||||||
|
///
|
||||||
|
/// Converts values to little endian on machines that work with BigEndian, however this is NOT TESTED yet.
|
||||||
|
public var convertedEndian: NumericValue {
|
||||||
|
self as! Self.NumericValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Double: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = UInt64
|
||||||
|
|
||||||
|
public var convertedEndian: UInt64 {
|
||||||
|
bitPattern.littleEndian
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Float32: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = UInt32
|
||||||
|
|
||||||
|
public var convertedEndian: UInt32 {
|
||||||
|
bitPattern.littleEndian
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Bool: Scalar, Verifiable {
|
||||||
|
public var convertedEndian: UInt8 {
|
||||||
|
self == true ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public typealias NumericValue = UInt8
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Int: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = Int
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Int8: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = Int8
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Int16: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = Int16
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Int32: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Int64: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UInt8: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = UInt8
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UInt16: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = UInt16
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UInt32: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = UInt32
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UInt64: Scalar, Verifiable {
|
||||||
|
public typealias NumericValue = UInt64
|
||||||
|
}
|
||||||
|
|
||||||
|
public func FlatBuffersVersion_24_12_23() {}
|
55
submodules/TelegramCore/FlatBuffers/Sources/Enum.swift
Normal file
55
submodules/TelegramCore/FlatBuffers/Sources/Enum.swift
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Enum is a protocol that all flatbuffers enums should conform to
|
||||||
|
/// Since it allows us to get the actual `ByteSize` and `Value` from
|
||||||
|
/// a swift enum.
|
||||||
|
public protocol Enum {
|
||||||
|
/// associatedtype that the type of the enum should conform to
|
||||||
|
associatedtype T: Scalar & Verifiable
|
||||||
|
/// Size of the current associatedtype in the enum
|
||||||
|
static var byteSize: Int { get }
|
||||||
|
/// The current value the enum hosts
|
||||||
|
var value: T { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Enum where Self: Verifiable {
|
||||||
|
|
||||||
|
/// Verifies that the current value is which the bounds of the buffer, and if
|
||||||
|
/// the current `Value` is aligned properly
|
||||||
|
/// - Parameters:
|
||||||
|
/// - verifier: Verifier that hosts the buffer
|
||||||
|
/// - position: Current position within the buffer
|
||||||
|
/// - type: The type of the object to be verified
|
||||||
|
/// - Throws: Errors coming from `inBuffer` function
|
||||||
|
public static func verify<T>(
|
||||||
|
_ verifier: inout Verifier,
|
||||||
|
at position: Int,
|
||||||
|
of type: T.Type) throws where T: Verifiable
|
||||||
|
{
|
||||||
|
try verifier.inBuffer(position: position, of: type.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UnionEnum is a Protocol that allows us to create Union type of enums
|
||||||
|
/// and their value initializers. Since an `init` was required by
|
||||||
|
/// the verifier
|
||||||
|
public protocol UnionEnum: Enum {
|
||||||
|
init?(value: T) throws
|
||||||
|
}
|
@ -0,0 +1,925 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// ``FlatBufferBuilder`` builds a `FlatBuffer` through manipulating its internal state.
|
||||||
|
///
|
||||||
|
/// This is done by creating a ``ByteBuffer`` that hosts the incoming data and
|
||||||
|
/// has a hardcoded growth limit of `2GiB` which is set by the Flatbuffers standards.
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// var builder = FlatBufferBuilder()
|
||||||
|
/// ```
|
||||||
|
/// The builder should be always created as a variable, since it would be passed into the writers
|
||||||
|
///
|
||||||
|
@frozen
|
||||||
|
public struct FlatBufferBuilder {
|
||||||
|
|
||||||
|
/// Storage for the Vtables used in the buffer are stored in here, so they would be written later in EndTable
|
||||||
|
@usableFromInline internal var _vtableStorage = VTableStorage()
|
||||||
|
/// Flatbuffer data will be written into
|
||||||
|
@usableFromInline internal var _bb: ByteBuffer
|
||||||
|
|
||||||
|
/// Reference Vtables that were already written to the buffer
|
||||||
|
private var _vtables: [UOffset] = []
|
||||||
|
/// A check if the buffer is being written into by a different table
|
||||||
|
private var isNested = false
|
||||||
|
/// Dictonary that stores a map of all the strings that were written to the buffer
|
||||||
|
private var stringOffsetMap: [String: Offset] = [:]
|
||||||
|
/// A check to see if finish(::) was ever called to retreive data object
|
||||||
|
private var finished = false
|
||||||
|
/// A check to see if the buffer should serialize Default values
|
||||||
|
private var serializeDefaults: Bool
|
||||||
|
|
||||||
|
/// Current alignment for the buffer
|
||||||
|
var _minAlignment: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
_bb.alignment = _minAlignment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives a read access to the buffer's size
|
||||||
|
public var size: UOffset { _bb.size }
|
||||||
|
|
||||||
|
#if !os(WASI)
|
||||||
|
/// Data representation of the buffer
|
||||||
|
///
|
||||||
|
/// Should only be used after ``finish(offset:addPrefix:)`` is called
|
||||||
|
public var data: Data {
|
||||||
|
assert(finished, "Data shouldn't be called before finish()")
|
||||||
|
return Data(
|
||||||
|
bytes: _bb.memory.advanced(by: _bb.writerIndex),
|
||||||
|
count: _bb.capacity &- _bb.writerIndex)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Returns the underlying bytes in the ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// Note: This should be used with caution.
|
||||||
|
public var fullSizedByteArray: [UInt8] {
|
||||||
|
let ptr = UnsafeBufferPointer(
|
||||||
|
start: _bb.memory.assumingMemoryBound(to: UInt8.self),
|
||||||
|
count: _bb.capacity)
|
||||||
|
return Array(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the written bytes into the ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// Should only be used after ``finish(offset:addPrefix:)`` is called
|
||||||
|
public var sizedByteArray: [UInt8] {
|
||||||
|
assert(finished, "Data shouldn't be called before finish()")
|
||||||
|
return _bb.underlyingBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the original ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// Returns the current buffer that was just created
|
||||||
|
/// with the offsets, and data written to it.
|
||||||
|
public var buffer: ByteBuffer { _bb }
|
||||||
|
|
||||||
|
/// Returns a newly created sized ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// returns a new buffer that is sized to the data written
|
||||||
|
/// to the main buffer
|
||||||
|
public var sizedBuffer: ByteBuffer {
|
||||||
|
assert(finished, "Data shouldn't be called before finish()")
|
||||||
|
return ByteBuffer(
|
||||||
|
memory: _bb.memory.advanced(by: _bb.reader),
|
||||||
|
count: Int(_bb.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
/// Initialize the buffer with a size
|
||||||
|
/// - Parameters:
|
||||||
|
/// - initialSize: Initial size for the buffer
|
||||||
|
/// - force: Allows default to be serialized into the buffer
|
||||||
|
///
|
||||||
|
/// This initializes a new builder with an initialSize that would initialize
|
||||||
|
/// a new ``ByteBuffer``. ``FlatBufferBuilder`` by default doesnt serialize defaults
|
||||||
|
/// however the builder can be force by passing true for `serializeDefaults`
|
||||||
|
public init(
|
||||||
|
initialSize: Int32 = 1024,
|
||||||
|
serializeDefaults force: Bool = false)
|
||||||
|
{
|
||||||
|
assert(initialSize > 0, "Size should be greater than zero!")
|
||||||
|
guard isLitteEndian else {
|
||||||
|
fatalError(
|
||||||
|
"Reading/Writing a buffer in big endian machine is not supported on swift")
|
||||||
|
}
|
||||||
|
serializeDefaults = force
|
||||||
|
_bb = ByteBuffer(initialSize: Int(initialSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the builder and the buffer from the written data.
|
||||||
|
mutating public func clear() {
|
||||||
|
_minAlignment = 0
|
||||||
|
isNested = false
|
||||||
|
stringOffsetMap.removeAll(keepingCapacity: true)
|
||||||
|
_vtables.removeAll(keepingCapacity: true)
|
||||||
|
_vtableStorage.clear()
|
||||||
|
_bb.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Create Tables
|
||||||
|
|
||||||
|
/// Checks if the required fields were serialized into the buffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - table: offset for the table
|
||||||
|
/// - fields: Array of all the important fields to be serialized
|
||||||
|
///
|
||||||
|
/// *NOTE: Never call this function, this is only supposed to be called
|
||||||
|
/// by the generated code*
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func require(table: Offset, fields: [Int32]) {
|
||||||
|
for index in stride(from: 0, to: fields.count, by: 1) {
|
||||||
|
let start = _bb.capacity &- Int(table.o)
|
||||||
|
let startTable = start &- Int(_bb.read(def: Int32.self, position: start))
|
||||||
|
let isOkay = _bb.read(
|
||||||
|
def: VOffset.self,
|
||||||
|
position: startTable &+ Int(fields[index])) != 0
|
||||||
|
assert(isOkay, "Flatbuffers requires the following field")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finished the buffer by adding the file id and then calling finish
|
||||||
|
/// - Parameters:
|
||||||
|
/// - offset: Offset of the table
|
||||||
|
/// - fileId: Takes the fileId
|
||||||
|
/// - prefix: if false it wont add the size of the buffer
|
||||||
|
///
|
||||||
|
/// ``finish(offset:fileId:addPrefix:)`` should be called at the end of creating
|
||||||
|
/// a table
|
||||||
|
/// ```swift
|
||||||
|
/// var root = SomeObject
|
||||||
|
/// .createObject(&builder,
|
||||||
|
/// name: nameOffset)
|
||||||
|
/// builder.finish(
|
||||||
|
/// offset: root,
|
||||||
|
/// fileId: "ax1a",
|
||||||
|
/// addPrefix: true)
|
||||||
|
/// ```
|
||||||
|
/// File id would append a file id name at the end of the written bytes before,
|
||||||
|
/// finishing the buffer.
|
||||||
|
///
|
||||||
|
/// Whereas, if `addPrefix` is true, the written bytes would
|
||||||
|
/// include the size of the current buffer.
|
||||||
|
mutating public func finish(
|
||||||
|
offset: Offset,
|
||||||
|
fileId: String,
|
||||||
|
addPrefix prefix: Bool = false)
|
||||||
|
{
|
||||||
|
let size = MemoryLayout<UOffset>.size
|
||||||
|
preAlign(
|
||||||
|
len: size &+ (prefix ? size : 0) &+ FileIdLength,
|
||||||
|
alignment: _minAlignment)
|
||||||
|
assert(fileId.count == FileIdLength, "Flatbuffers requires file id to be 4")
|
||||||
|
_bb.push(string: fileId, len: 4)
|
||||||
|
finish(offset: offset, addPrefix: prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finished the buffer by adding the file id, offset, and prefix to it.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - offset: Offset of the table
|
||||||
|
/// - prefix: if false it wont add the size of the buffer
|
||||||
|
///
|
||||||
|
/// ``finish(offset:addPrefix:)`` should be called at the end of creating
|
||||||
|
/// a table
|
||||||
|
/// ```swift
|
||||||
|
/// var root = SomeObject
|
||||||
|
/// .createObject(&builder,
|
||||||
|
/// name: nameOffset)
|
||||||
|
/// builder.finish(
|
||||||
|
/// offset: root,
|
||||||
|
/// addPrefix: true)
|
||||||
|
/// ```
|
||||||
|
/// If `addPrefix` is true, the written bytes would
|
||||||
|
/// include the size of the current buffer.
|
||||||
|
mutating public func finish(
|
||||||
|
offset: Offset,
|
||||||
|
addPrefix prefix: Bool = false)
|
||||||
|
{
|
||||||
|
notNested()
|
||||||
|
let size = MemoryLayout<UOffset>.size
|
||||||
|
preAlign(len: size &+ (prefix ? size : 0), alignment: _minAlignment)
|
||||||
|
push(element: refer(to: offset.o))
|
||||||
|
if prefix { push(element: _bb.size) }
|
||||||
|
_vtableStorage.clear()
|
||||||
|
finished = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ``startTable(with:)`` will let the builder know, that a new object is being serialized.
|
||||||
|
///
|
||||||
|
/// The function will fatalerror if called while there is another object being serialized.
|
||||||
|
/// ```swift
|
||||||
|
/// let start = Monster
|
||||||
|
/// .startMonster(&fbb)
|
||||||
|
/// ```
|
||||||
|
/// - Parameter numOfFields: Number of elements to be written to the buffer
|
||||||
|
/// - Returns: Offset of the newly started table
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func startTable(with numOfFields: Int) -> UOffset {
|
||||||
|
notNested()
|
||||||
|
isNested = true
|
||||||
|
_vtableStorage.start(count: numOfFields)
|
||||||
|
return _bb.size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ``endTable(at:)`` will let the ``FlatBufferBuilder`` know that the
|
||||||
|
/// object that's written to it is completed
|
||||||
|
///
|
||||||
|
/// This would be called after all the elements are serialized,
|
||||||
|
/// it will add the current vtable into the ``ByteBuffer``.
|
||||||
|
/// The functions will `fatalError` in case the object is called
|
||||||
|
/// without ``startTable(with:)``, or the object has exceeded the limit of 2GB.
|
||||||
|
///
|
||||||
|
/// - Parameter startOffset:Start point of the object written
|
||||||
|
/// - returns: The root of the table
|
||||||
|
mutating public func endTable(at startOffset: UOffset) -> UOffset {
|
||||||
|
assert(isNested, "Calling endtable without calling starttable")
|
||||||
|
let sizeofVoffset = MemoryLayout<VOffset>.size
|
||||||
|
let vTableOffset = push(element: SOffset(0))
|
||||||
|
|
||||||
|
let tableObjectSize = vTableOffset &- startOffset
|
||||||
|
assert(tableObjectSize < 0x10000, "Buffer can't grow beyond 2 Gigabytes")
|
||||||
|
let _max = Int(_vtableStorage.maxOffset) &+ sizeofVoffset
|
||||||
|
|
||||||
|
_bb.fill(padding: _max)
|
||||||
|
_bb.write(
|
||||||
|
value: VOffset(tableObjectSize),
|
||||||
|
index: _bb.writerIndex &+ sizeofVoffset,
|
||||||
|
direct: true)
|
||||||
|
_bb.write(value: VOffset(_max), index: _bb.writerIndex, direct: true)
|
||||||
|
|
||||||
|
var itr = 0
|
||||||
|
while itr < _vtableStorage.writtenIndex {
|
||||||
|
let loaded = _vtableStorage.load(at: itr)
|
||||||
|
itr = itr &+ _vtableStorage.size
|
||||||
|
guard loaded.offset != 0 else { continue }
|
||||||
|
let _index = (_bb.writerIndex &+ Int(loaded.position))
|
||||||
|
_bb.write(
|
||||||
|
value: VOffset(vTableOffset &- loaded.offset),
|
||||||
|
index: _index,
|
||||||
|
direct: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
_vtableStorage.clear()
|
||||||
|
let vt_use = _bb.size
|
||||||
|
|
||||||
|
var isAlreadyAdded: Int?
|
||||||
|
|
||||||
|
let vt2 = _bb.memory.advanced(by: _bb.writerIndex)
|
||||||
|
let len2 = vt2.load(fromByteOffset: 0, as: Int16.self)
|
||||||
|
|
||||||
|
for index in stride(from: 0, to: _vtables.count, by: 1) {
|
||||||
|
let position = _bb.capacity &- Int(_vtables[index])
|
||||||
|
let vt1 = _bb.memory.advanced(by: position)
|
||||||
|
let len1 = _bb.read(def: Int16.self, position: position)
|
||||||
|
if len2 != len1 || 0 != memcmp(vt1, vt2, Int(len2)) { continue }
|
||||||
|
|
||||||
|
isAlreadyAdded = Int(_vtables[index])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if let offset = isAlreadyAdded {
|
||||||
|
let vTableOff = Int(vTableOffset)
|
||||||
|
let space = _bb.capacity &- vTableOff
|
||||||
|
_bb.write(value: Int32(offset &- vTableOff), index: space, direct: true)
|
||||||
|
_bb.pop(_bb.capacity &- space)
|
||||||
|
} else {
|
||||||
|
_bb.write(value: Int32(vt_use &- vTableOffset), index: Int(vTableOffset))
|
||||||
|
_vtables.append(_bb.size)
|
||||||
|
}
|
||||||
|
isNested = false
|
||||||
|
return vTableOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Builds Buffer
|
||||||
|
|
||||||
|
/// Asserts to see if the object is not nested
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating internal func notNested() {
|
||||||
|
assert(!isNested, "Object serialization must not be nested")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the minimuim alignment of the buffer
|
||||||
|
/// - Parameter size: size of the current alignment
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating internal func minAlignment(size: Int) {
|
||||||
|
if size > _minAlignment {
|
||||||
|
_minAlignment = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the padding for the current element
|
||||||
|
/// - Parameters:
|
||||||
|
/// - bufSize: Current size of the buffer + the offset of the object to be written
|
||||||
|
/// - elementSize: Element size
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating internal func padding(
|
||||||
|
bufSize: UInt32,
|
||||||
|
elementSize: UInt32) -> UInt32
|
||||||
|
{
|
||||||
|
((~bufSize) &+ 1) & (elementSize - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prealigns the buffer before writting a new object into the buffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - len:Length of the object
|
||||||
|
/// - alignment: Alignment type
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating internal func preAlign(len: Int, alignment: Int) {
|
||||||
|
minAlignment(size: alignment)
|
||||||
|
_bb.fill(padding: Int(padding(
|
||||||
|
bufSize: _bb.size &+ UOffset(len),
|
||||||
|
elementSize: UOffset(alignment))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prealigns the buffer before writting a new object into the buffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - len: Length of the object
|
||||||
|
/// - type: Type of the object to be written
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating internal func preAlign<T: Scalar>(len: Int, type: T.Type) {
|
||||||
|
preAlign(len: len, alignment: MemoryLayout<T>.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refers to an object that's written in the buffer
|
||||||
|
/// - Parameter off: the objects index value
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating internal func refer(to off: UOffset) -> UOffset {
|
||||||
|
let size = MemoryLayout<UOffset>.size
|
||||||
|
preAlign(len: size, alignment: size)
|
||||||
|
return _bb.size &- off &+ UInt32(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks the elements written into the buffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - offset: The offset of the element witten
|
||||||
|
/// - position: The position of the element
|
||||||
|
@inline(__always)
|
||||||
|
@usableFromInline
|
||||||
|
mutating internal func track(offset: UOffset, at position: VOffset) {
|
||||||
|
_vtableStorage.add(loc: (offset: offset, position: position))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Inserting Vectors
|
||||||
|
|
||||||
|
/// ``startVector(_:elementSize:)`` creates a new vector within buffer
|
||||||
|
///
|
||||||
|
/// The function checks if there is a current object being written, if
|
||||||
|
/// the check passes it creates a buffer alignment of `length * elementSize`
|
||||||
|
/// ```swift
|
||||||
|
/// builder.startVector(
|
||||||
|
/// int32Values.count, elementSize: 4)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - len: Length of vector to be created
|
||||||
|
/// - elementSize: Size of object type to be written
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func startVector(_ len: Int, elementSize: Int) {
|
||||||
|
notNested()
|
||||||
|
isNested = true
|
||||||
|
preAlign(len: len &* elementSize, type: UOffset.self)
|
||||||
|
preAlign(len: len &* elementSize, alignment: elementSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ``endVector(len:)`` ends the currently created vector
|
||||||
|
///
|
||||||
|
/// Calling ``endVector(len:)`` requires the length, of the current
|
||||||
|
/// vector. The length would be pushed to indicate the count of numbers
|
||||||
|
/// within the vector. If ``endVector(len:)`` is called without
|
||||||
|
/// ``startVector(_:elementSize:)`` it asserts.
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// let vectorOffset = builder.
|
||||||
|
/// endVector(len: int32Values.count)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameter len: Length of the buffer
|
||||||
|
/// - Returns: Returns the current ``Offset`` in the ``ByteBuffer``
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func endVector(len: Int) -> Offset {
|
||||||
|
assert(isNested, "Calling endVector without calling startVector")
|
||||||
|
isNested = false
|
||||||
|
return Offset(offset: push(element: Int32(len)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a vector of type ``Scalar`` into the ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// ``createVector(_:)-4swl0`` writes a vector of type Scalars into
|
||||||
|
/// ``ByteBuffer``. This is a convenient method instead of calling,
|
||||||
|
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
|
||||||
|
/// ```swift
|
||||||
|
/// let vectorOffset = builder.
|
||||||
|
/// createVector([1, 2, 3, 4])
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The underlying implementation simply calls ``createVector(_:size:)-4lhrv``
|
||||||
|
///
|
||||||
|
/// - Parameter elements: elements to be written into the buffer
|
||||||
|
/// - returns: ``Offset`` of the vector
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func createVector<T: Scalar>(_ elements: [T]) -> Offset {
|
||||||
|
createVector(elements, size: elements.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a vector of type Scalar in the buffer
|
||||||
|
///
|
||||||
|
/// ``createVector(_:)-4swl0`` writes a vector of type Scalars into
|
||||||
|
/// ``ByteBuffer``. This is a convenient method instead of calling,
|
||||||
|
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
|
||||||
|
/// ```swift
|
||||||
|
/// let vectorOffset = builder.
|
||||||
|
/// createVector([1, 2, 3, 4], size: 4)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameter elements: Elements to be written into the buffer
|
||||||
|
/// - Parameter size: Count of elements
|
||||||
|
/// - returns: ``Offset`` of the vector
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func createVector<T: Scalar>(
|
||||||
|
_ elements: [T],
|
||||||
|
size: Int) -> Offset
|
||||||
|
{
|
||||||
|
let size = size
|
||||||
|
startVector(size, elementSize: MemoryLayout<T>.size)
|
||||||
|
_bb.push(elements: elements)
|
||||||
|
return endVector(len: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.0) && !os(WASI)
|
||||||
|
@inline(__always)
|
||||||
|
/// Creates a vector of bytes in the buffer.
|
||||||
|
///
|
||||||
|
/// Allows creating a vector from `Data` without copying to a `[UInt8]`
|
||||||
|
///
|
||||||
|
/// - Parameter bytes: bytes to be written into the buffer
|
||||||
|
/// - Returns: ``Offset`` of the vector
|
||||||
|
mutating public func createVector(bytes: ContiguousBytes) -> Offset {
|
||||||
|
let size = bytes.withUnsafeBytes { ptr in ptr.count }
|
||||||
|
startVector(size, elementSize: MemoryLayout<UInt8>.size)
|
||||||
|
_bb.push(bytes: bytes)
|
||||||
|
return endVector(len: size)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Creates a vector of type ``Enum`` into the ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// ``createVector(_:)-9h189`` writes a vector of type ``Enum`` into
|
||||||
|
/// ``ByteBuffer``. This is a convenient method instead of calling,
|
||||||
|
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
|
||||||
|
/// ```swift
|
||||||
|
/// let vectorOffset = builder.
|
||||||
|
/// createVector([.swift, .cpp])
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The underlying implementation simply calls ``createVector(_:size:)-7cx6z``
|
||||||
|
///
|
||||||
|
/// - Parameter elements: elements to be written into the buffer
|
||||||
|
/// - returns: ``Offset`` of the vector
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func createVector<T: Enum>(_ elements: [T]) -> Offset {
|
||||||
|
createVector(elements, size: elements.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a vector of type ``Enum`` into the ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// ``createVector(_:)-9h189`` writes a vector of type ``Enum`` into
|
||||||
|
/// ``ByteBuffer``. This is a convenient method instead of calling,
|
||||||
|
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
|
||||||
|
/// ```swift
|
||||||
|
/// let vectorOffset = builder.
|
||||||
|
/// createVector([.swift, .cpp])
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameter elements: Elements to be written into the buffer
|
||||||
|
/// - Parameter size: Count of elements
|
||||||
|
/// - returns: ``Offset`` of the vector
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func createVector<T: Enum>(
|
||||||
|
_ elements: [T],
|
||||||
|
size: Int) -> Offset
|
||||||
|
{
|
||||||
|
let size = size
|
||||||
|
startVector(size, elementSize: T.byteSize)
|
||||||
|
for index in stride(from: elements.count, to: 0, by: -1) {
|
||||||
|
_bb.push(value: elements[index &- 1].value, len: T.byteSize)
|
||||||
|
}
|
||||||
|
return endVector(len: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a vector of already written offsets
|
||||||
|
///
|
||||||
|
/// ``createVector(ofOffsets:)`` creates a vector of ``Offset`` into
|
||||||
|
/// ``ByteBuffer``. This is a convenient method instead of calling,
|
||||||
|
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``.
|
||||||
|
///
|
||||||
|
/// The underlying implementation simply calls ``createVector(ofOffsets:len:)``
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// let namesOffsets = builder.
|
||||||
|
/// createVector(ofOffsets: [name1, name2])
|
||||||
|
/// ```
|
||||||
|
/// - Parameter offsets: Array of offsets of type ``Offset``
|
||||||
|
/// - returns: ``Offset`` of the vector
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func createVector(ofOffsets offsets: [Offset]) -> Offset {
|
||||||
|
createVector(ofOffsets: offsets, len: offsets.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a vector of already written offsets
|
||||||
|
///
|
||||||
|
/// ``createVector(ofOffsets:)`` creates a vector of ``Offset`` into
|
||||||
|
/// ``ByteBuffer``. This is a convenient method instead of calling,
|
||||||
|
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// let namesOffsets = builder.
|
||||||
|
/// createVector(ofOffsets: [name1, name2])
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameter offsets: Array of offsets of type ``Offset``
|
||||||
|
/// - Parameter size: Count of elements
|
||||||
|
/// - returns: ``Offset`` of the vector
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func createVector(
|
||||||
|
ofOffsets offsets: [Offset],
|
||||||
|
len: Int) -> Offset
|
||||||
|
{
|
||||||
|
startVector(len, elementSize: MemoryLayout<Offset>.size)
|
||||||
|
for index in stride(from: offsets.count, to: 0, by: -1) {
|
||||||
|
push(element: offsets[index &- 1])
|
||||||
|
}
|
||||||
|
return endVector(len: len)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a vector of strings
|
||||||
|
///
|
||||||
|
/// ``createVector(ofStrings:)`` creates a vector of `String` into
|
||||||
|
/// ``ByteBuffer``. This is a convenient method instead of manually
|
||||||
|
/// creating the string offsets, you simply pass it to this function
|
||||||
|
/// and it would write the strings into the ``ByteBuffer``.
|
||||||
|
/// After that it calls ``createVector(ofOffsets:)``
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// let namesOffsets = builder.
|
||||||
|
/// createVector(ofStrings: ["Name", "surname"])
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameter str: Array of string
|
||||||
|
/// - returns: ``Offset`` of the vector
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func createVector(ofStrings str: [String]) -> Offset {
|
||||||
|
var offsets: [Offset] = []
|
||||||
|
for index in stride(from: 0, to: str.count, by: 1) {
|
||||||
|
offsets.append(create(string: str[index]))
|
||||||
|
}
|
||||||
|
return createVector(ofOffsets: offsets)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a vector of type ``NativeStruct``.
|
||||||
|
///
|
||||||
|
/// Any swift struct in the generated code, should confirm to
|
||||||
|
/// ``NativeStruct``. Since the generated swift structs are padded
|
||||||
|
/// to the `FlatBuffers` standards.
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// let offsets = builder.
|
||||||
|
/// createVector(ofStructs: [NativeStr(num: 1), NativeStr(num: 2)])
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameter structs: A vector of ``NativeStruct``
|
||||||
|
/// - Returns: ``Offset`` of the vector
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func createVector<T: NativeStruct>(ofStructs structs: [T])
|
||||||
|
-> Offset
|
||||||
|
{
|
||||||
|
startVector(
|
||||||
|
structs.count * MemoryLayout<T>.size,
|
||||||
|
elementSize: MemoryLayout<T>.alignment)
|
||||||
|
_bb.push(elements: structs)
|
||||||
|
return endVector(len: structs.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Inserting Structs
|
||||||
|
|
||||||
|
/// Writes a ``NativeStruct`` into the ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// Adds a native struct that's build and padded according
|
||||||
|
/// to `FlatBuffers` standards. with a predefined position.
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// let offset = builder.create(
|
||||||
|
/// struct: NativeStr(num: 1),
|
||||||
|
/// position: 10)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - s: ``NativeStruct`` to be inserted into the ``ByteBuffer``
|
||||||
|
/// - position: The predefined position of the object
|
||||||
|
/// - Returns: ``Offset`` of written struct
|
||||||
|
@inline(__always)
|
||||||
|
@discardableResult
|
||||||
|
mutating public func create<T: NativeStruct>(
|
||||||
|
struct s: T, position: VOffset) -> Offset
|
||||||
|
{
|
||||||
|
let offset = create(struct: s)
|
||||||
|
_vtableStorage.add(
|
||||||
|
loc: (offset: _bb.size, position: VOffset(position)))
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a ``NativeStruct`` into the ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// Adds a native struct that's build and padded according
|
||||||
|
/// to `FlatBuffers` standards, directly into the buffer without
|
||||||
|
/// a predefined position.
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// let offset = builder.create(
|
||||||
|
/// struct: NativeStr(num: 1))
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - s: ``NativeStruct`` to be inserted into the ``ByteBuffer``
|
||||||
|
/// - Returns: ``Offset`` of written struct
|
||||||
|
@inline(__always)
|
||||||
|
@discardableResult
|
||||||
|
mutating public func create<T: NativeStruct>(
|
||||||
|
struct s: T) -> Offset
|
||||||
|
{
|
||||||
|
let size = MemoryLayout<T>.size
|
||||||
|
preAlign(len: size, alignment: MemoryLayout<T>.alignment)
|
||||||
|
_bb.push(struct: s, size: size)
|
||||||
|
return Offset(offset: _bb.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Inserting Strings
|
||||||
|
|
||||||
|
/// Insets a string into the buffer of type `UTF8`
|
||||||
|
///
|
||||||
|
/// Adds a swift string into ``ByteBuffer`` by encoding it
|
||||||
|
/// using `UTF8`
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// let nameOffset = builder
|
||||||
|
/// .create(string: "welcome")
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameter str: String to be serialized
|
||||||
|
/// - returns: ``Offset`` of inserted string
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func create(string str: String?) -> Offset {
|
||||||
|
guard let str = str else { return Offset() }
|
||||||
|
let len = str.utf8.count
|
||||||
|
notNested()
|
||||||
|
preAlign(len: len &+ 1, type: UOffset.self)
|
||||||
|
_bb.fill(padding: 1)
|
||||||
|
_bb.push(string: str, len: len)
|
||||||
|
push(element: UOffset(len))
|
||||||
|
return Offset(offset: _bb.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insets a shared string into the buffer of type `UTF8`
|
||||||
|
///
|
||||||
|
/// Adds a swift string into ``ByteBuffer`` by encoding it
|
||||||
|
/// using `UTF8`. The function will check if the string,
|
||||||
|
/// is already written to the ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// let nameOffset = builder
|
||||||
|
/// .createShared(string: "welcome")
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// let secondOffset = builder
|
||||||
|
/// .createShared(string: "welcome")
|
||||||
|
///
|
||||||
|
/// assert(nameOffset.o == secondOffset.o)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - Parameter str: String to be serialized
|
||||||
|
/// - returns: ``Offset`` of inserted string
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func createShared(string str: String?) -> Offset {
|
||||||
|
guard let str = str else { return Offset() }
|
||||||
|
if let offset = stringOffsetMap[str] {
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
let offset = create(string: str)
|
||||||
|
stringOffsetMap[str] = offset
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Inseting offsets
|
||||||
|
|
||||||
|
/// Writes the ``Offset`` of an already written table
|
||||||
|
///
|
||||||
|
/// Writes the ``Offset`` of a table if not empty into the
|
||||||
|
/// ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - offset: ``Offset`` of another object to be written
|
||||||
|
/// - position: The predefined position of the object
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func add(offset: Offset, at position: VOffset) {
|
||||||
|
if offset.isEmpty { return }
|
||||||
|
add(element: refer(to: offset.o), def: 0, at: position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a value of type ``Offset`` into the ``ByteBuffer``
|
||||||
|
/// - Parameter o: ``Offset``
|
||||||
|
/// - returns: Current position of the ``Offset``
|
||||||
|
@inline(__always)
|
||||||
|
@discardableResult
|
||||||
|
mutating public func push(element o: Offset) -> UOffset {
|
||||||
|
push(element: refer(to: o.o))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Inserting Scalars to Buffer
|
||||||
|
|
||||||
|
/// Writes a ``Scalar`` value into ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// ``add(element:def:at:)`` takes in a default value, and current value
|
||||||
|
/// and the position within the `VTable`. The default value would not
|
||||||
|
/// be serialized if the value is the same as the current value or
|
||||||
|
/// `serializeDefaults` is equal to false.
|
||||||
|
///
|
||||||
|
/// If serializing defaults is important ``init(initialSize:serializeDefaults:)``,
|
||||||
|
/// passing true for `serializeDefaults` would do the job.
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// // Adds 10 to the buffer
|
||||||
|
/// builder.add(element: Int(10), def: 1, position 12)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// *NOTE: Never call this manually*
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - element: Element to insert
|
||||||
|
/// - def: Default value for that element
|
||||||
|
/// - position: The predefined position of the element
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func add<T: Scalar>(
|
||||||
|
element: T,
|
||||||
|
def: T,
|
||||||
|
at position: VOffset)
|
||||||
|
{
|
||||||
|
if element == def && !serializeDefaults { return }
|
||||||
|
track(offset: push(element: element), at: position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a optional ``Scalar`` value into ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// Takes an optional value to be written into the ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// *NOTE: Never call this manually*
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - element: Optional element of type scalar
|
||||||
|
/// - position: The predefined position of the element
|
||||||
|
@inline(__always)
|
||||||
|
mutating public func add<T: Scalar>(element: T?, at position: VOffset) {
|
||||||
|
guard let element = element else { return }
|
||||||
|
track(offset: push(element: element), at: position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a values of type ``Scalar`` into the ``ByteBuffer``
|
||||||
|
///
|
||||||
|
/// *NOTE: Never call this manually*
|
||||||
|
///
|
||||||
|
/// - Parameter element: Element to insert
|
||||||
|
/// - returns: position of the Element
|
||||||
|
@inline(__always)
|
||||||
|
@discardableResult
|
||||||
|
mutating public func push<T: Scalar>(element: T) -> UOffset {
|
||||||
|
let size = MemoryLayout<T>.size
|
||||||
|
preAlign(
|
||||||
|
len: size,
|
||||||
|
alignment: size)
|
||||||
|
_bb.push(value: element, len: size)
|
||||||
|
return _bb.size
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FlatBufferBuilder: CustomDebugStringConvertible {
|
||||||
|
|
||||||
|
public var debugDescription: String {
|
||||||
|
"""
|
||||||
|
buffer debug:
|
||||||
|
\(_bb)
|
||||||
|
builder debug:
|
||||||
|
{ finished: \(finished), serializeDefaults: \(
|
||||||
|
serializeDefaults), isNested: \(isNested) }
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias FieldLoc = (offset: UOffset, position: VOffset)
|
||||||
|
|
||||||
|
/// VTableStorage is a class to contain the VTable buffer that would be serialized into buffer
|
||||||
|
@usableFromInline
|
||||||
|
internal class VTableStorage {
|
||||||
|
/// Memory check since deallocating each time we want to clear would be expensive
|
||||||
|
/// and memory leaks would happen if we dont deallocate the first allocated memory.
|
||||||
|
/// memory is promised to be available before adding `FieldLoc`
|
||||||
|
private var memoryInUse = false
|
||||||
|
/// Size of FieldLoc in memory
|
||||||
|
let size = MemoryLayout<FieldLoc>.stride
|
||||||
|
/// Memeory buffer
|
||||||
|
var memory: UnsafeMutableRawBufferPointer!
|
||||||
|
/// Capacity of the current buffer
|
||||||
|
var capacity: Int = 0
|
||||||
|
/// Maximuim offset written to the class
|
||||||
|
var maxOffset: VOffset = 0
|
||||||
|
/// number of fields written into the buffer
|
||||||
|
var numOfFields: Int = 0
|
||||||
|
/// Last written Index
|
||||||
|
var writtenIndex: Int = 0
|
||||||
|
|
||||||
|
/// Creates the memory to store the buffer in
|
||||||
|
@usableFromInline
|
||||||
|
@inline(__always)
|
||||||
|
init() {
|
||||||
|
memory = UnsafeMutableRawBufferPointer.allocate(
|
||||||
|
byteCount: 0,
|
||||||
|
alignment: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@inline(__always)
|
||||||
|
deinit {
|
||||||
|
memory.deallocate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a buffer with byte count of fieldloc.size * count of field numbers
|
||||||
|
/// - Parameter count: number of fields to be written
|
||||||
|
@inline(__always)
|
||||||
|
func start(count: Int) {
|
||||||
|
assert(count >= 0, "number of fields should NOT be negative")
|
||||||
|
let capacity = count &* size
|
||||||
|
ensure(space: capacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a FieldLoc into the buffer, which would track how many have been written,
|
||||||
|
/// and max offset
|
||||||
|
/// - Parameter loc: Location of encoded element
|
||||||
|
@inline(__always)
|
||||||
|
func add(loc: FieldLoc) {
|
||||||
|
memory.baseAddress?.advanced(by: writtenIndex).storeBytes(
|
||||||
|
of: loc,
|
||||||
|
as: FieldLoc.self)
|
||||||
|
writtenIndex = writtenIndex &+ size
|
||||||
|
numOfFields = numOfFields &+ 1
|
||||||
|
maxOffset = max(loc.position, maxOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the data stored related to the encoded buffer
|
||||||
|
@inline(__always)
|
||||||
|
func clear() {
|
||||||
|
maxOffset = 0
|
||||||
|
numOfFields = 0
|
||||||
|
writtenIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that the buffer has enough space instead of recreating the buffer each time.
|
||||||
|
/// - Parameter space: space required for the new vtable
|
||||||
|
@inline(__always)
|
||||||
|
func ensure(space: Int) {
|
||||||
|
guard space &+ writtenIndex > capacity else { return }
|
||||||
|
memory.deallocate()
|
||||||
|
memory = UnsafeMutableRawBufferPointer.allocate(
|
||||||
|
byteCount: space,
|
||||||
|
alignment: size)
|
||||||
|
capacity = space
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads an object of type `FieldLoc` from buffer memory
|
||||||
|
/// - Parameter index: index of element
|
||||||
|
/// - Returns: a FieldLoc at index
|
||||||
|
@inline(__always)
|
||||||
|
func load(at index: Int) -> FieldLoc {
|
||||||
|
memory.load(fromByteOffset: index, as: FieldLoc.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// NativeStruct is a protocol that indicates if the struct is a native `swift` struct
|
||||||
|
/// since now we will be serializing native structs into the buffer.
|
||||||
|
public protocol NativeStruct {}
|
||||||
|
|
||||||
|
/// FlatbuffersInitializable is a protocol that allows any object to be
|
||||||
|
/// Initialized from a ByteBuffer
|
||||||
|
public protocol FlatbuffersInitializable {
|
||||||
|
/// Any flatbuffers object that confirms to this protocol is going to be
|
||||||
|
/// initializable through this initializer
|
||||||
|
init(_ bb: ByteBuffer, o: Int32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FlatbufferObject structures all the Flatbuffers objects
|
||||||
|
public protocol FlatBufferObject: FlatbuffersInitializable {
|
||||||
|
var __buffer: ByteBuffer! { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ``ObjectAPIPacker`` is a protocol that allows object to pack and unpack from a
|
||||||
|
/// ``NativeObject`` to a flatbuffers Object and vice versa.
|
||||||
|
public protocol ObjectAPIPacker {
|
||||||
|
/// associatedtype to the object that should be unpacked.
|
||||||
|
associatedtype T
|
||||||
|
|
||||||
|
/// ``pack(_:obj:)-3ptws`` tries to pacs the variables of a native Object into the `ByteBuffer` by using
|
||||||
|
/// a FlatBufferBuilder
|
||||||
|
/// - Parameters:
|
||||||
|
/// - builder: FlatBufferBuilder that will host incoming data
|
||||||
|
/// - obj: Object of associatedtype to the current implementer
|
||||||
|
///
|
||||||
|
/// ``pack(_:obj:)-3ptws`` can be called by passing through an already initialized ``FlatBufferBuilder``
|
||||||
|
/// or it can be called by using the public API that will create a new ``FlatBufferBuilder``
|
||||||
|
static func pack(_ builder: inout FlatBufferBuilder, obj: inout T?) -> Offset
|
||||||
|
|
||||||
|
/// ``pack(_:obj:)-20ipk`` packs the variables of a native Object into the `ByteBuffer` by using
|
||||||
|
/// the FlatBufferBuilder
|
||||||
|
/// - Parameters:
|
||||||
|
/// - builder: FlatBufferBuilder that will host incoming data
|
||||||
|
/// - obj: Object of associatedtype to the current implementer
|
||||||
|
///
|
||||||
|
/// ``pack(_:obj:)-20ipk`` can be called by passing through an already initialized ``FlatBufferBuilder``
|
||||||
|
/// or it can be called by using the public API that will create a new ``FlatBufferBuilder``
|
||||||
|
static func pack(_ builder: inout FlatBufferBuilder, obj: inout T) -> Offset
|
||||||
|
|
||||||
|
/// ``unpack()`` unpacks a ``FlatBuffers`` object into a Native swift object.
|
||||||
|
mutating func unpack() -> T
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// FlatBuffersUtils hosts some utility functions that might be useful
|
||||||
|
public enum FlatBuffersUtils {
|
||||||
|
|
||||||
|
/// Gets the size of the prefix
|
||||||
|
/// - Parameter bb: Flatbuffer object
|
||||||
|
public static func getSizePrefix(bb: ByteBuffer) -> Int32 {
|
||||||
|
bb.read(def: Int32.self, position: bb.reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the prefix by duplicating the Flatbuffer this call is expensive since its
|
||||||
|
/// creates a new buffer use `readPrefixedSizeCheckedRoot` instead
|
||||||
|
/// unless a completely new buffer is required
|
||||||
|
/// - Parameter bb: Flatbuffer object
|
||||||
|
///
|
||||||
|
///
|
||||||
|
public static func removeSizePrefix(bb: ByteBuffer) -> ByteBuffer {
|
||||||
|
bb.duplicate(removing: MemoryLayout<Int32>.size)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Collection of thrown from the Flatbuffer verifier
|
||||||
|
public enum FlatbuffersErrors: Error, Equatable {
|
||||||
|
|
||||||
|
/// Thrown when trying to verify a buffer that doesnt have the length of an ID
|
||||||
|
case bufferDoesntContainID
|
||||||
|
/// Thrown when verifying a file id that doesnt match buffer id
|
||||||
|
case bufferIdDidntMatchPassedId
|
||||||
|
/// Prefixed size doesnt match the current (readable) buffer size
|
||||||
|
case prefixedSizeNotEqualToBufferSize
|
||||||
|
/// Thrown when buffer is bigger than the allowed 2GiB
|
||||||
|
case exceedsMaxSizeAllowed
|
||||||
|
/// Thrown when there is an missaligned pointer at position
|
||||||
|
/// of type
|
||||||
|
case missAlignedPointer(position: Int, type: String)
|
||||||
|
/// Thrown when trying to read a value that goes out of the
|
||||||
|
/// current buffer bounds
|
||||||
|
case outOfBounds(position: UInt, end: Int)
|
||||||
|
/// Thrown when the signed offset is out of the bounds of the
|
||||||
|
/// current buffer
|
||||||
|
case signedOffsetOutOfBounds(offset: Int, position: Int)
|
||||||
|
/// Thrown when a required field doesnt exist within the buffer
|
||||||
|
case requiredFieldDoesntExist(position: VOffset, name: String)
|
||||||
|
/// Thrown when a string is missing its NULL Terminator `\0`,
|
||||||
|
/// this can be disabled in the `VerifierOptions`
|
||||||
|
case missingNullTerminator(position: Int, str: String?)
|
||||||
|
/// Thrown when the verifier has reached the maximum tables allowed,
|
||||||
|
/// this can be disabled in the `VerifierOptions`
|
||||||
|
case maximumTables
|
||||||
|
/// Thrown when the verifier has reached the maximum depth allowed,
|
||||||
|
/// this can be disabled in the `VerifierOptions`
|
||||||
|
case maximumDepth
|
||||||
|
/// Thrown when the verifier is presented with an unknown union case
|
||||||
|
case unknownUnionCase
|
||||||
|
/// thrown when a value for a union is not found within the buffer
|
||||||
|
case valueNotFound(key: Int?, keyName: String, field: Int?, fieldName: String)
|
||||||
|
/// thrown when the size of the keys vector doesnt match fields vector
|
||||||
|
case unionVectorSize(
|
||||||
|
keyVectorSize: Int,
|
||||||
|
fieldVectorSize: Int,
|
||||||
|
unionKeyName: String,
|
||||||
|
fieldName: String)
|
||||||
|
case apparentSizeTooLarge
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !os(WASI)
|
||||||
|
|
||||||
|
extension FlatbuffersErrors {
|
||||||
|
public static func == (
|
||||||
|
lhs: FlatbuffersErrors,
|
||||||
|
rhs: FlatbuffersErrors) -> Bool
|
||||||
|
{
|
||||||
|
lhs.localizedDescription == rhs.localizedDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Int {
|
||||||
|
|
||||||
|
/// Moves the current int into the nearest power of two
|
||||||
|
///
|
||||||
|
/// This is used since the UnsafeMutableRawPointer will face issues when writing/reading
|
||||||
|
/// if the buffer alignment exceeds that actual size of the buffer
|
||||||
|
var convertToPowerofTwo: Int {
|
||||||
|
guard self > 0 else { return 1 }
|
||||||
|
var n = UOffset(self)
|
||||||
|
|
||||||
|
#if arch(arm) || arch(i386)
|
||||||
|
let max = UInt32(Int.max)
|
||||||
|
#else
|
||||||
|
let max = UInt32.max
|
||||||
|
#endif
|
||||||
|
|
||||||
|
n -= 1
|
||||||
|
n |= n >> 1
|
||||||
|
n |= n >> 2
|
||||||
|
n |= n >> 4
|
||||||
|
n |= n >> 8
|
||||||
|
n |= n >> 16
|
||||||
|
if n != max {
|
||||||
|
n += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return Int(n)
|
||||||
|
}
|
||||||
|
}
|
65
submodules/TelegramCore/FlatBuffers/Sources/Message.swift
Normal file
65
submodules/TelegramCore/FlatBuffers/Sources/Message.swift
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// FlatBufferGRPCMessage protocol that should allow us to invoke
|
||||||
|
/// initializers directly from the GRPC generated code
|
||||||
|
public protocol FlatBufferGRPCMessage {
|
||||||
|
|
||||||
|
/// Raw pointer which would be pointing to the beginning of the readable bytes
|
||||||
|
var rawPointer: UnsafeMutableRawPointer { get }
|
||||||
|
|
||||||
|
/// Size of readable bytes in the buffer
|
||||||
|
var size: Int { get }
|
||||||
|
|
||||||
|
init(byteBuffer: ByteBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message is a wrapper around Buffers to to able to send Flatbuffers `Buffers` through the
|
||||||
|
/// GRPC library
|
||||||
|
public struct Message<T: FlatBufferObject>: FlatBufferGRPCMessage {
|
||||||
|
internal var buffer: ByteBuffer
|
||||||
|
|
||||||
|
/// Returns the an object of type T that would be read from the buffer
|
||||||
|
public var object: T {
|
||||||
|
T.init(
|
||||||
|
buffer,
|
||||||
|
o: Int32(buffer.read(def: UOffset.self, position: buffer.reader)) +
|
||||||
|
Int32(buffer.reader))
|
||||||
|
}
|
||||||
|
|
||||||
|
public var rawPointer: UnsafeMutableRawPointer {
|
||||||
|
buffer.memory.advanced(by: buffer.reader) }
|
||||||
|
|
||||||
|
public var size: Int { Int(buffer.size) }
|
||||||
|
|
||||||
|
/// Initializes the message with the type Flatbuffer.Bytebuffer that is transmitted over
|
||||||
|
/// GRPC
|
||||||
|
/// - Parameter byteBuffer: Flatbuffer ByteBuffer object
|
||||||
|
public init(byteBuffer: ByteBuffer) {
|
||||||
|
buffer = byteBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the message by copying the buffer to the message to be sent.
|
||||||
|
/// from the builder
|
||||||
|
/// - Parameter builder: FlatbufferBuilder that has the bytes created in
|
||||||
|
/// - Note: Use `builder.finish(offset)` before passing the builder without prefixing anything to it
|
||||||
|
public init(builder: inout FlatBufferBuilder) {
|
||||||
|
buffer = builder.sizedBuffer
|
||||||
|
builder.clear()
|
||||||
|
}
|
||||||
|
}
|
84
submodules/TelegramCore/FlatBuffers/Sources/Mutable.swift
Normal file
84
submodules/TelegramCore/FlatBuffers/Sources/Mutable.swift
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Mutable is a protocol that allows us to mutate Scalar values within a ``ByteBuffer``
|
||||||
|
public protocol Mutable {
|
||||||
|
/// makes Flatbuffer accessed within the Protocol
|
||||||
|
var bb: ByteBuffer { get }
|
||||||
|
/// makes position of the ``Table``/``Struct`` accessed within the Protocol
|
||||||
|
var position: Int32 { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Mutable {
|
||||||
|
|
||||||
|
/// Mutates the memory in the buffer, this is only called from the access function of ``Table`` and ``struct``
|
||||||
|
/// - Parameters:
|
||||||
|
/// - value: New value to be inserted to the buffer
|
||||||
|
/// - index: index of the Element
|
||||||
|
func mutate<T: Scalar>(value: T, o: Int32) -> Bool {
|
||||||
|
guard o != 0 else { return false }
|
||||||
|
bb.write(value: value, index: Int(o), direct: true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Mutable where Self == Table {
|
||||||
|
|
||||||
|
/// Mutates a value by calling mutate with respect to the position in a ``Table``
|
||||||
|
/// - Parameters:
|
||||||
|
/// - value: New value to be inserted to the buffer
|
||||||
|
/// - index: index of the Element
|
||||||
|
public func mutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
|
||||||
|
guard index != 0 else { return false }
|
||||||
|
return mutate(value: value, o: index + position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Directly mutates the element by calling mutate
|
||||||
|
///
|
||||||
|
/// Mutates the Element at index ignoring the current position by calling mutate
|
||||||
|
/// - Parameters:
|
||||||
|
/// - value: New value to be inserted to the buffer
|
||||||
|
/// - index: index of the Element
|
||||||
|
public func directMutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
|
||||||
|
mutate(value: value, o: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Mutable where Self == Struct {
|
||||||
|
|
||||||
|
/// Mutates a value by calling mutate with respect to the position in the struct
|
||||||
|
/// - Parameters:
|
||||||
|
/// - value: New value to be inserted to the buffer
|
||||||
|
/// - index: index of the Element
|
||||||
|
public func mutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
|
||||||
|
mutate(value: value, o: index + position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Directly mutates the element by calling mutate
|
||||||
|
///
|
||||||
|
/// Mutates the Element at index ignoring the current position by calling mutate
|
||||||
|
/// - Parameters:
|
||||||
|
/// - value: New value to be inserted to the buffer
|
||||||
|
/// - index: index of the Element
|
||||||
|
public func directMutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
|
||||||
|
mutate(value: value, o: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Struct: Mutable {}
|
||||||
|
extension Table: Mutable {}
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// NativeObject is a protocol that all of the `Object-API` generated code should be
|
||||||
|
/// conforming to since it allows developers the ease of use to pack and unpack their
|
||||||
|
/// Flatbuffers objects
|
||||||
|
public protocol NativeObject {}
|
||||||
|
|
||||||
|
extension NativeObject {
|
||||||
|
|
||||||
|
/// Serialize is a helper function that serializes the data from the Object API to a bytebuffer directly th
|
||||||
|
/// - Parameter type: Type of the Flatbuffer object
|
||||||
|
/// - Returns: returns the encoded sized ByteBuffer
|
||||||
|
public func serialize<T: ObjectAPIPacker>(type: T.Type) -> ByteBuffer
|
||||||
|
where T.T == Self
|
||||||
|
{
|
||||||
|
var builder = FlatBufferBuilder(initialSize: 1024)
|
||||||
|
return serialize(builder: &builder, type: type.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize is a helper function that serializes the data from the Object API to a bytebuffer directly.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - builder: A FlatBufferBuilder
|
||||||
|
/// - type: Type of the Flatbuffer object
|
||||||
|
/// - Returns: returns the encoded sized ByteBuffer
|
||||||
|
/// - Note: The `serialize(builder:type)` can be considered as a function that allows you to create smaller builder instead of the default `1024`.
|
||||||
|
/// It can be considered less expensive in terms of memory allocation
|
||||||
|
public func serialize<T: ObjectAPIPacker>(
|
||||||
|
builder: inout FlatBufferBuilder,
|
||||||
|
type: T.Type) -> ByteBuffer where T.T == Self
|
||||||
|
{
|
||||||
|
var s = self
|
||||||
|
let root = type.pack(&builder, obj: &s)
|
||||||
|
builder.finish(offset: root)
|
||||||
|
return builder.sizedBuffer
|
||||||
|
}
|
||||||
|
}
|
28
submodules/TelegramCore/FlatBuffers/Sources/Offset.swift
Normal file
28
submodules/TelegramCore/FlatBuffers/Sources/Offset.swift
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Offset object for all the Objects that are written into the buffer
|
||||||
|
public struct Offset {
|
||||||
|
/// Offset of the object in the buffer
|
||||||
|
public var o: UOffset
|
||||||
|
/// Returns false if the offset is equal to zero
|
||||||
|
public var isEmpty: Bool { o == 0 }
|
||||||
|
|
||||||
|
public init(offset: UOffset) { o = offset }
|
||||||
|
public init() { o = 0 }
|
||||||
|
}
|
116
submodules/TelegramCore/FlatBuffers/Sources/Root.swift
Normal file
116
submodules/TelegramCore/FlatBuffers/Sources/Root.swift
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Takes in a prefixed sized buffer, where the prefixed size would be skipped.
|
||||||
|
/// And would verify that the buffer passed is a valid `Flatbuffers` Object.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - byteBuffer: Buffer that needs to be checked and read
|
||||||
|
/// - options: Verifier options
|
||||||
|
/// - Throws: FlatbuffersErrors
|
||||||
|
/// - Returns: Returns a valid, checked Flatbuffers object
|
||||||
|
///
|
||||||
|
/// ``getPrefixedSizeCheckedRoot(byteBuffer:options:)`` would skip the first Bytes in
|
||||||
|
/// the ``ByteBuffer`` and verifies the buffer by calling ``getCheckedRoot(byteBuffer:options:)``
|
||||||
|
public func getPrefixedSizeCheckedRoot<T: FlatBufferObject & Verifiable>(
|
||||||
|
byteBuffer: inout ByteBuffer,
|
||||||
|
fileId: String? = nil,
|
||||||
|
options: VerifierOptions = .init()) throws -> T
|
||||||
|
{
|
||||||
|
byteBuffer.skipPrefix()
|
||||||
|
return try getCheckedRoot(
|
||||||
|
byteBuffer: &byteBuffer,
|
||||||
|
fileId: fileId,
|
||||||
|
options: options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes in a prefixed sized buffer, where we check if the sized buffer is equal to prefix size.
|
||||||
|
/// And would verify that the buffer passed is a valid `Flatbuffers` Object.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - byteBuffer: Buffer that needs to be checked and read
|
||||||
|
/// - options: Verifier options
|
||||||
|
/// - Throws: FlatbuffersErrors
|
||||||
|
/// - Returns: Returns a valid, checked Flatbuffers object
|
||||||
|
///
|
||||||
|
/// ``getPrefixedSizeCheckedRoot(byteBuffer:options:)`` would skip the first Bytes in
|
||||||
|
/// the ``ByteBuffer`` and verifies the buffer by calling ``getCheckedRoot(byteBuffer:options:)``
|
||||||
|
public func getCheckedPrefixedSizeRoot<T: FlatBufferObject & Verifiable>(
|
||||||
|
byteBuffer: inout ByteBuffer,
|
||||||
|
fileId: String? = nil,
|
||||||
|
options: VerifierOptions = .init()) throws -> T
|
||||||
|
{
|
||||||
|
let prefix = byteBuffer.skipPrefix()
|
||||||
|
if prefix != byteBuffer.size {
|
||||||
|
throw FlatbuffersErrors.prefixedSizeNotEqualToBufferSize
|
||||||
|
}
|
||||||
|
return try getCheckedRoot(
|
||||||
|
byteBuffer: &byteBuffer,
|
||||||
|
fileId: fileId,
|
||||||
|
options: options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes in a prefixed sized buffer, where the prefixed size would be skipped.
|
||||||
|
/// Returns a `NON-Checked` flatbuffers object
|
||||||
|
/// - Parameter byteBuffer: Buffer that contains data
|
||||||
|
/// - Returns: Returns a Flatbuffers object
|
||||||
|
///
|
||||||
|
/// ``getPrefixedSizeCheckedRoot(byteBuffer:options:)`` would skip the first Bytes in
|
||||||
|
/// the ``ByteBuffer`` and then calls ``getRoot(byteBuffer:)``
|
||||||
|
public func getPrefixedSizeRoot<T: FlatBufferObject>(
|
||||||
|
byteBuffer: inout ByteBuffer)
|
||||||
|
-> T
|
||||||
|
{
|
||||||
|
byteBuffer.skipPrefix()
|
||||||
|
return getRoot(byteBuffer: &byteBuffer)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifies that the buffer passed is a valid `Flatbuffers` Object.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - byteBuffer: Buffer that needs to be checked and read
|
||||||
|
/// - options: Verifier options
|
||||||
|
/// - Throws: FlatbuffersErrors
|
||||||
|
/// - Returns: Returns a valid, checked Flatbuffers object
|
||||||
|
///
|
||||||
|
/// ``getCheckedRoot(byteBuffer:options:)`` Takes in a ``ByteBuffer`` and verifies
|
||||||
|
/// that by creating a ``Verifier`` and checkes if all the `Bytes` and correctly aligned
|
||||||
|
/// and within the ``ByteBuffer`` range.
|
||||||
|
public func getCheckedRoot<T: FlatBufferObject & Verifiable>(
|
||||||
|
byteBuffer: inout ByteBuffer,
|
||||||
|
fileId: String? = nil,
|
||||||
|
options: VerifierOptions = .init()) throws -> T
|
||||||
|
{
|
||||||
|
var verifier = try Verifier(buffer: &byteBuffer, options: options)
|
||||||
|
if let fileId = fileId {
|
||||||
|
try verifier.verify(id: fileId)
|
||||||
|
}
|
||||||
|
try ForwardOffset<T>.verify(&verifier, at: 0, of: T.self)
|
||||||
|
return T.init(
|
||||||
|
byteBuffer,
|
||||||
|
o: Int32(byteBuffer.read(def: UOffset.self, position: byteBuffer.reader)) +
|
||||||
|
Int32(byteBuffer.reader))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a `NON-Checked` flatbuffers object
|
||||||
|
/// - Parameter byteBuffer: Buffer that contains data
|
||||||
|
/// - Returns: Returns a Flatbuffers object
|
||||||
|
public func getRoot<T: FlatBufferObject>(byteBuffer: inout ByteBuffer) -> T {
|
||||||
|
T.init(
|
||||||
|
byteBuffer,
|
||||||
|
o: Int32(byteBuffer.read(def: UOffset.self, position: byteBuffer.reader)) +
|
||||||
|
Int32(byteBuffer.reader))
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension String: Verifiable {
|
||||||
|
|
||||||
|
/// Verifies that the current value is which the bounds of the buffer, and if
|
||||||
|
/// the current `Value` is aligned properly
|
||||||
|
/// - Parameters:
|
||||||
|
/// - verifier: Verifier that hosts the buffer
|
||||||
|
/// - position: Current position within the buffer
|
||||||
|
/// - type: The type of the object to be verified
|
||||||
|
/// - Throws: Errors coming from `inBuffer`, `missingNullTerminator` and `outOfBounds`
|
||||||
|
public static func verify<T>(
|
||||||
|
_ verifier: inout Verifier,
|
||||||
|
at position: Int,
|
||||||
|
of type: T.Type) throws where T: Verifiable
|
||||||
|
{
|
||||||
|
|
||||||
|
let range = try String.verifyRange(&verifier, at: position, of: UInt8.self)
|
||||||
|
/// Safe &+ since we already check for overflow in verify range
|
||||||
|
let stringLen = range.start &+ range.count
|
||||||
|
|
||||||
|
if stringLen >= verifier.capacity {
|
||||||
|
throw FlatbuffersErrors.outOfBounds(
|
||||||
|
position: UInt(clamping: stringLen.magnitude),
|
||||||
|
end: verifier.capacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
let isNullTerminated = verifier._buffer.read(
|
||||||
|
def: UInt8.self,
|
||||||
|
position: stringLen) == 0
|
||||||
|
|
||||||
|
if !verifier._options._ignoreMissingNullTerminators && !isNullTerminated {
|
||||||
|
let str = verifier._buffer.readString(at: range.start, count: range.count)
|
||||||
|
throw FlatbuffersErrors.missingNullTerminator(
|
||||||
|
position: position,
|
||||||
|
str: str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String: FlatbuffersInitializable {
|
||||||
|
|
||||||
|
/// Initailizes a string from a Flatbuffers ByteBuffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - bb: ByteBuffer containing the readable string
|
||||||
|
/// - o: Current position
|
||||||
|
public init(_ bb: ByteBuffer, o: Int32) {
|
||||||
|
let v = Int(o)
|
||||||
|
let count = bb.read(def: Int32.self, position: v)
|
||||||
|
self = bb.readString(
|
||||||
|
at: MemoryLayout<Int32>.size + v,
|
||||||
|
count: Int(count)) ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String: ObjectAPIPacker {
|
||||||
|
|
||||||
|
public static func pack(
|
||||||
|
_ builder: inout FlatBufferBuilder,
|
||||||
|
obj: inout String?) -> Offset
|
||||||
|
{
|
||||||
|
guard var obj = obj else { return Offset() }
|
||||||
|
return pack(&builder, obj: &obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func pack(
|
||||||
|
_ builder: inout FlatBufferBuilder,
|
||||||
|
obj: inout String) -> Offset
|
||||||
|
{
|
||||||
|
builder.create(string: obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
public mutating func unpack() -> String {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String: NativeObject {
|
||||||
|
|
||||||
|
public func serialize<T: ObjectAPIPacker>(type: T.Type) -> ByteBuffer
|
||||||
|
where T.T == Self
|
||||||
|
{
|
||||||
|
fatalError("serialize should never be called from string directly")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func serialize<T: ObjectAPIPacker>(
|
||||||
|
builder: inout FlatBufferBuilder,
|
||||||
|
type: T.Type) -> ByteBuffer where T.T == Self
|
||||||
|
{
|
||||||
|
fatalError("serialize should never be called from string directly")
|
||||||
|
}
|
||||||
|
}
|
47
submodules/TelegramCore/FlatBuffers/Sources/Struct.swift
Normal file
47
submodules/TelegramCore/FlatBuffers/Sources/Struct.swift
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Struct is a representation of a mutable `Flatbuffers` struct
|
||||||
|
/// since native structs are value types and cant be mutated
|
||||||
|
@frozen
|
||||||
|
public struct Struct {
|
||||||
|
|
||||||
|
/// Hosting Bytebuffer
|
||||||
|
public private(set) var bb: ByteBuffer
|
||||||
|
/// Current position of the struct
|
||||||
|
public private(set) var position: Int32
|
||||||
|
|
||||||
|
/// Initializer for a mutable flatbuffers struct
|
||||||
|
/// - Parameters:
|
||||||
|
/// - bb: Current hosting Bytebuffer
|
||||||
|
/// - position: Current position for the struct in the ByteBuffer
|
||||||
|
public init(bb: ByteBuffer, position: Int32 = 0) {
|
||||||
|
self.bb = bb
|
||||||
|
self.position = position
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads data from the buffer directly at offset O
|
||||||
|
/// - Parameters:
|
||||||
|
/// - type: Type of data to be read
|
||||||
|
/// - o: Current offset of the data
|
||||||
|
/// - Returns: Data of Type T that conforms to type Scalar
|
||||||
|
public func readBuffer<T: Scalar>(of type: T.Type, at o: Int32) -> T {
|
||||||
|
let r = bb.read(def: T.self, position: Int(o + position))
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
236
submodules/TelegramCore/FlatBuffers/Sources/Table.swift
Normal file
236
submodules/TelegramCore/FlatBuffers/Sources/Table.swift
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// `Table` is a Flatbuffers object that can read,
|
||||||
|
/// mutate scalar fields within a valid flatbuffers buffer
|
||||||
|
@frozen
|
||||||
|
public struct Table {
|
||||||
|
|
||||||
|
/// Hosting Bytebuffer
|
||||||
|
public private(set) var bb: ByteBuffer
|
||||||
|
/// Current position of the table within the buffer
|
||||||
|
public private(set) var position: Int32
|
||||||
|
|
||||||
|
/// Initializer for the table interface to allow generated code to read
|
||||||
|
/// data from memory
|
||||||
|
/// - Parameters:
|
||||||
|
/// - bb: ByteBuffer that stores data
|
||||||
|
/// - position: Current table position
|
||||||
|
/// - Note: This will `CRASH` if read on a big endian machine
|
||||||
|
public init(bb: ByteBuffer, position: Int32 = 0) {
|
||||||
|
guard isLitteEndian else {
|
||||||
|
fatalError(
|
||||||
|
"Reading/Writing a buffer in big endian machine is not supported on swift")
|
||||||
|
}
|
||||||
|
self.bb = bb
|
||||||
|
self.position = position
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the offset of the current field within the buffer by reading
|
||||||
|
/// the vtable
|
||||||
|
/// - Parameter o: current offset
|
||||||
|
/// - Returns: offset of field within buffer
|
||||||
|
public func offset(_ o: Int32) -> Int32 {
|
||||||
|
let vtable = position - bb.read(def: Int32.self, position: Int(position))
|
||||||
|
return o < bb
|
||||||
|
.read(def: VOffset.self, position: Int(vtable)) ? Int32(bb.read(
|
||||||
|
def: Int16.self,
|
||||||
|
position: Int(vtable + o))) : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the indirect offset of the current stored object
|
||||||
|
/// (applicable only for object arrays)
|
||||||
|
/// - Parameter o: current offset
|
||||||
|
/// - Returns: offset of field within buffer
|
||||||
|
public func indirect(_ o: Int32) -> Int32 {
|
||||||
|
o + bb.read(def: Int32.self, position: Int(o))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// String reads from the buffer with respect to position of the current table.
|
||||||
|
/// - Parameter offset: Offset of the string
|
||||||
|
public func string(at offset: Int32) -> String? {
|
||||||
|
directString(at: offset + position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Direct string reads from the buffer disregarding the position of the table.
|
||||||
|
/// It would be preferable to use string unless the current position of the table
|
||||||
|
/// is not needed
|
||||||
|
/// - Parameter offset: Offset of the string
|
||||||
|
public func directString(at offset: Int32) -> String? {
|
||||||
|
var offset = offset
|
||||||
|
offset += bb.read(def: Int32.self, position: Int(offset))
|
||||||
|
let count = bb.read(def: Int32.self, position: Int(offset))
|
||||||
|
let position = Int(offset) + MemoryLayout<Int32>.size
|
||||||
|
return bb.readString(at: position, count: Int(count))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads from the buffer with respect to the position in the table.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - type: Type of Element that needs to be read from the buffer
|
||||||
|
/// - o: Offset of the Element
|
||||||
|
public func readBuffer<T>(of type: T.Type, at o: Int32) -> T {
|
||||||
|
directRead(of: T.self, offset: o + position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads from the buffer disregarding the position of the table.
|
||||||
|
/// It would be used when reading from an
|
||||||
|
/// ```
|
||||||
|
/// let offset = __t.offset(10)
|
||||||
|
/// //Only used when the we already know what is the
|
||||||
|
/// // position in the table since __t.vector(at:)
|
||||||
|
/// // returns the index with respect to the position
|
||||||
|
/// __t.directRead(of: Byte.self,
|
||||||
|
/// offset: __t.vector(at: offset) + index * 1)
|
||||||
|
/// ```
|
||||||
|
/// - Parameters:
|
||||||
|
/// - type: Type of Element that needs to be read from the buffer
|
||||||
|
/// - o: Offset of the Element
|
||||||
|
public func directRead<T>(of type: T.Type, offset o: Int32) -> T {
|
||||||
|
let r = bb.read(def: T.self, position: Int(o))
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns that current `Union` object at a specific offset
|
||||||
|
/// by adding offset to the current position of table
|
||||||
|
/// - Parameter o: offset
|
||||||
|
/// - Returns: A flatbuffers object
|
||||||
|
public func union<T: FlatbuffersInitializable>(_ o: Int32) -> T {
|
||||||
|
let o = o + position
|
||||||
|
return directUnion(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a direct `Union` object at a specific offset
|
||||||
|
/// - Parameter o: offset
|
||||||
|
/// - Returns: A flatbuffers object
|
||||||
|
public func directUnion<T: FlatbuffersInitializable>(_ o: Int32) -> T {
|
||||||
|
T.init(bb, o: o + bb.read(def: Int32.self, position: Int(o)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a vector of type T at a specific offset
|
||||||
|
/// This should only be used by `Scalars`
|
||||||
|
/// - Parameter off: Readable offset
|
||||||
|
/// - Returns: Returns a vector of type [T]
|
||||||
|
public func getVector<T>(at off: Int32) -> [T]? {
|
||||||
|
let o = offset(off)
|
||||||
|
guard o != 0 else { return nil }
|
||||||
|
return bb.readSlice(index: Int(vector(at: o)), count: Int(vector(count: o)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vector count gets the count of Elements within the array
|
||||||
|
/// - Parameter o: start offset of the vector
|
||||||
|
/// - returns: Count of elements
|
||||||
|
public func vector(count o: Int32) -> Int32 {
|
||||||
|
var o = o
|
||||||
|
o += position
|
||||||
|
o += bb.read(def: Int32.self, position: Int(o))
|
||||||
|
return bb.read(def: Int32.self, position: Int(o))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vector start index in the buffer
|
||||||
|
/// - Parameter o:start offset of the vector
|
||||||
|
/// - returns: the start index of the vector
|
||||||
|
public func vector(at o: Int32) -> Int32 {
|
||||||
|
var o = o
|
||||||
|
o += position
|
||||||
|
return o + bb.read(def: Int32.self, position: Int(o)) + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reading an indirect offset of a table.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - o: position within the buffer
|
||||||
|
/// - fbb: ByteBuffer
|
||||||
|
/// - Returns: table offset
|
||||||
|
static public func indirect(_ o: Int32, _ fbb: ByteBuffer) -> Int32 {
|
||||||
|
o + fbb.read(def: Int32.self, position: Int(o))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a vtable value according to an table Offset and a field offset
|
||||||
|
/// - Parameters:
|
||||||
|
/// - o: offset relative to entire buffer
|
||||||
|
/// - vOffset: Field offset within a vtable
|
||||||
|
/// - fbb: ByteBuffer
|
||||||
|
/// - Returns: an position of a field
|
||||||
|
static public func offset(
|
||||||
|
_ o: Int32,
|
||||||
|
vOffset: Int32,
|
||||||
|
fbb: ByteBuffer) -> Int32
|
||||||
|
{
|
||||||
|
let vTable = Int32(fbb.capacity) - o
|
||||||
|
return vTable + Int32(fbb.read(
|
||||||
|
def: Int16.self,
|
||||||
|
position: Int(vTable + vOffset - fbb.read(
|
||||||
|
def: Int32.self,
|
||||||
|
position: Int(vTable)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares two objects at offset A and offset B within a ByteBuffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - off1: first offset to compare
|
||||||
|
/// - off2: second offset to compare
|
||||||
|
/// - fbb: Bytebuffer
|
||||||
|
/// - Returns: returns the difference between
|
||||||
|
static public func compare(
|
||||||
|
_ off1: Int32,
|
||||||
|
_ off2: Int32,
|
||||||
|
fbb: ByteBuffer) -> Int32
|
||||||
|
{
|
||||||
|
let memorySize = Int32(MemoryLayout<Int32>.size)
|
||||||
|
let _off1 = off1 + fbb.read(def: Int32.self, position: Int(off1))
|
||||||
|
let _off2 = off2 + fbb.read(def: Int32.self, position: Int(off2))
|
||||||
|
let len1 = fbb.read(def: Int32.self, position: Int(_off1))
|
||||||
|
let len2 = fbb.read(def: Int32.self, position: Int(_off2))
|
||||||
|
let startPos1 = _off1 + memorySize
|
||||||
|
let startPos2 = _off2 + memorySize
|
||||||
|
let minValue = min(len1, len2)
|
||||||
|
for i in 0...minValue {
|
||||||
|
let b1 = fbb.read(def: Int8.self, position: Int(i + startPos1))
|
||||||
|
let b2 = fbb.read(def: Int8.self, position: Int(i + startPos2))
|
||||||
|
if b1 != b2 {
|
||||||
|
return Int32(b2 - b1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len1 - len2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares two objects at offset A and array of `Bytes` within a ByteBuffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - off1: Offset to compare to
|
||||||
|
/// - key: bytes array to compare to
|
||||||
|
/// - fbb: Bytebuffer
|
||||||
|
/// - Returns: returns the difference between
|
||||||
|
static public func compare(
|
||||||
|
_ off1: Int32,
|
||||||
|
_ key: [Byte],
|
||||||
|
fbb: ByteBuffer) -> Int32
|
||||||
|
{
|
||||||
|
let memorySize = Int32(MemoryLayout<Int32>.size)
|
||||||
|
let _off1 = off1 + fbb.read(def: Int32.self, position: Int(off1))
|
||||||
|
let len1 = fbb.read(def: Int32.self, position: Int(_off1))
|
||||||
|
let len2 = Int32(key.count)
|
||||||
|
let startPos1 = _off1 + memorySize
|
||||||
|
let minValue = min(len1, len2)
|
||||||
|
for i in 0..<minValue {
|
||||||
|
let b = fbb.read(def: Int8.self, position: Int(i + startPos1))
|
||||||
|
let byte = key[Int(i)]
|
||||||
|
if b != byte {
|
||||||
|
return Int32(b - Int8(byte))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len1 - len2
|
||||||
|
}
|
||||||
|
}
|
203
submodules/TelegramCore/FlatBuffers/Sources/TableVerifier.swift
Normal file
203
submodules/TelegramCore/FlatBuffers/Sources/TableVerifier.swift
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// `TableVerifier` verifies a table object is within a provided memory.
|
||||||
|
/// It checks if all the objects for a specific generated table, are within
|
||||||
|
/// the bounds of the buffer, aligned.
|
||||||
|
public struct TableVerifier {
|
||||||
|
|
||||||
|
/// position of current table in `ByteBuffer`
|
||||||
|
fileprivate var _position: Int
|
||||||
|
|
||||||
|
/// Current VTable position
|
||||||
|
fileprivate var _vtable: Int
|
||||||
|
|
||||||
|
/// Length of current VTable
|
||||||
|
fileprivate var _vtableLength: Int
|
||||||
|
|
||||||
|
/// `Verifier` object created in the base verifable call.
|
||||||
|
fileprivate var _verifier: Verifier
|
||||||
|
|
||||||
|
/// Creates a `TableVerifier` verifier that allows the Flatbuffer object
|
||||||
|
/// to verify the buffer before accessing any of the data.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - position: Current table Position
|
||||||
|
/// - vtable: Current `VTable` position
|
||||||
|
/// - vtableLength: Current `VTable` length
|
||||||
|
/// - verifier: `Verifier` Object that caches the data of the verifiable object
|
||||||
|
internal init(
|
||||||
|
position: Int,
|
||||||
|
vtable: Int,
|
||||||
|
vtableLength: Int,
|
||||||
|
verifier: inout Verifier)
|
||||||
|
{
|
||||||
|
_position = position
|
||||||
|
_vtable = vtable
|
||||||
|
_vtableLength = vtableLength
|
||||||
|
_verifier = verifier
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dereference the current object position from the `VTable`
|
||||||
|
/// - Parameter field: Current VTable refrence to position.
|
||||||
|
/// - Throws: A `FlatbuffersErrors` incase the voffset is not aligned/outOfBounds/apparentSizeTooLarge
|
||||||
|
/// - Returns: An optional position for current field
|
||||||
|
internal mutating func dereference(_ field: VOffset) throws -> Int? {
|
||||||
|
if field >= _vtableLength {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reading the offset for the field needs to be read.
|
||||||
|
let offset: VOffset = try _verifier.getValue(
|
||||||
|
at: Int(clamping: _vtable &+ Int(field)))
|
||||||
|
|
||||||
|
if offset > 0 {
|
||||||
|
return Int(clamping: _position &+ Int(offset))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visits all the fields within the table to validate the integrity
|
||||||
|
/// of the data
|
||||||
|
/// - Parameters:
|
||||||
|
/// - field: voffset of the current field to be read
|
||||||
|
/// - fieldName: fieldname to report data Errors.
|
||||||
|
/// - required: If the field has to be available in the buffer
|
||||||
|
/// - type: Type of field to be read
|
||||||
|
/// - Throws: A `FlatbuffersErrors` where the field is corrupt
|
||||||
|
public mutating func visit<T>(
|
||||||
|
field: VOffset,
|
||||||
|
fieldName: String,
|
||||||
|
required: Bool,
|
||||||
|
type: T.Type) throws where T: Verifiable
|
||||||
|
{
|
||||||
|
let derefValue = try dereference(field)
|
||||||
|
|
||||||
|
if let value = derefValue {
|
||||||
|
try T.verify(&_verifier, at: value, of: T.self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if required {
|
||||||
|
throw FlatbuffersErrors.requiredFieldDoesntExist(
|
||||||
|
position: field,
|
||||||
|
name: fieldName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visits all the fields for a union object within the table to
|
||||||
|
/// validate the integrity of the data
|
||||||
|
/// - Parameters:
|
||||||
|
/// - key: Current Key Voffset
|
||||||
|
/// - field: Current field Voffset
|
||||||
|
/// - unionKeyName: Union key name
|
||||||
|
/// - fieldName: Field key name
|
||||||
|
/// - required: indicates if an object is required to be present
|
||||||
|
/// - completion: Completion is a handler that WILL be called in the generated
|
||||||
|
/// - Throws: A `FlatbuffersErrors` where the field is corrupt
|
||||||
|
public mutating func visit<T>(
|
||||||
|
unionKey key: VOffset,
|
||||||
|
unionField field: VOffset,
|
||||||
|
unionKeyName: String,
|
||||||
|
fieldName: String,
|
||||||
|
required: Bool,
|
||||||
|
completion: @escaping (inout Verifier, T, Int) throws -> Void) throws
|
||||||
|
where T: UnionEnum
|
||||||
|
{
|
||||||
|
let keyPos = try dereference(key)
|
||||||
|
let valPos = try dereference(field)
|
||||||
|
|
||||||
|
if keyPos == nil && valPos == nil {
|
||||||
|
if required {
|
||||||
|
throw FlatbuffersErrors.requiredFieldDoesntExist(
|
||||||
|
position: key,
|
||||||
|
name: unionKeyName)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _key = keyPos,
|
||||||
|
let _val = valPos
|
||||||
|
{
|
||||||
|
/// verifiying that the key is within the buffer
|
||||||
|
try T.T.verify(&_verifier, at: _key, of: T.T.self)
|
||||||
|
guard let _enum = try T.init(value: _verifier._buffer.read(
|
||||||
|
def: T.T.self,
|
||||||
|
position: _key)) else
|
||||||
|
{
|
||||||
|
throw FlatbuffersErrors.unknownUnionCase
|
||||||
|
}
|
||||||
|
/// we are assuming that Unions will always be of type Uint8
|
||||||
|
try completion(
|
||||||
|
&_verifier,
|
||||||
|
_enum,
|
||||||
|
_val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw FlatbuffersErrors.valueNotFound(
|
||||||
|
key: keyPos,
|
||||||
|
keyName: unionKeyName,
|
||||||
|
field: valPos,
|
||||||
|
fieldName: fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visits and validates all the objects within a union vector
|
||||||
|
/// - Parameters:
|
||||||
|
/// - key: Current Key Voffset
|
||||||
|
/// - field: Current field Voffset
|
||||||
|
/// - unionKeyName: Union key name
|
||||||
|
/// - fieldName: Field key name
|
||||||
|
/// - required: indicates if an object is required to be present
|
||||||
|
/// - completion: Completion is a handler that WILL be called in the generated
|
||||||
|
/// - Throws: A `FlatbuffersErrors` where the field is corrupt
|
||||||
|
public mutating func visitUnionVector<T>(
|
||||||
|
unionKey key: VOffset,
|
||||||
|
unionField field: VOffset,
|
||||||
|
unionKeyName: String,
|
||||||
|
fieldName: String,
|
||||||
|
required: Bool,
|
||||||
|
completion: @escaping (inout Verifier, T, Int) throws -> Void) throws
|
||||||
|
where T: UnionEnum
|
||||||
|
{
|
||||||
|
let keyVectorPosition = try dereference(key)
|
||||||
|
let offsetVectorPosition = try dereference(field)
|
||||||
|
|
||||||
|
if let keyPos = keyVectorPosition,
|
||||||
|
let valPos = offsetVectorPosition
|
||||||
|
{
|
||||||
|
try UnionVector<T>.verify(
|
||||||
|
&_verifier,
|
||||||
|
keyPosition: keyPos,
|
||||||
|
fieldPosition: valPos,
|
||||||
|
unionKeyName: unionKeyName,
|
||||||
|
fieldName: fieldName,
|
||||||
|
completion: completion)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if required {
|
||||||
|
throw FlatbuffersErrors.requiredFieldDoesntExist(
|
||||||
|
position: field,
|
||||||
|
name: fieldName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finishs the current Table verifier, and subtracts the current
|
||||||
|
/// table from the incremented depth.
|
||||||
|
public mutating func finish() {
|
||||||
|
_verifier.finish()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// `VerifierOptions` is a set of options to verify a flatbuffer
|
||||||
|
public struct VerifierOptions {
|
||||||
|
|
||||||
|
/// Maximum `Apparent` size if the buffer can be expanded into a DAG tree
|
||||||
|
internal var _maxApparentSize: UOffset
|
||||||
|
|
||||||
|
/// Maximum table count allowed in a buffer
|
||||||
|
internal var _maxTableCount: UOffset
|
||||||
|
|
||||||
|
/// Maximum depth allowed in a buffer
|
||||||
|
internal var _maxDepth: UOffset
|
||||||
|
|
||||||
|
/// Ignoring missing null terminals in strings
|
||||||
|
internal var _ignoreMissingNullTerminators: Bool
|
||||||
|
|
||||||
|
/// initializes the set of options for the verifier
|
||||||
|
/// - Parameters:
|
||||||
|
/// - maxDepth: Maximum depth allowed in a buffer
|
||||||
|
/// - maxTableCount: Maximum table count allowed in a buffer
|
||||||
|
/// - maxApparentSize: Maximum `Apparent` size if the buffer can be expanded into a DAG tree
|
||||||
|
/// - ignoreMissingNullTerminators: Ignoring missing null terminals in strings *Currently not supported in swift*
|
||||||
|
public init(
|
||||||
|
maxDepth: UOffset = 64,
|
||||||
|
maxTableCount: UOffset = 1000000,
|
||||||
|
maxApparentSize: UOffset = 1 << 31,
|
||||||
|
ignoreMissingNullTerminators: Bool = false)
|
||||||
|
{
|
||||||
|
_maxDepth = maxDepth
|
||||||
|
_maxTableCount = maxTableCount
|
||||||
|
_maxApparentSize = maxApparentSize
|
||||||
|
_ignoreMissingNullTerminators = ignoreMissingNullTerminators
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
213
submodules/TelegramCore/FlatBuffers/Sources/Verifiable.swift
Normal file
213
submodules/TelegramCore/FlatBuffers/Sources/Verifiable.swift
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Verifiable is a protocol all swift flatbuffers object should conform to,
|
||||||
|
/// since swift is similar to `cpp` and `rust` where the data is read directly
|
||||||
|
/// from `unsafeMemory` thus the need to verify if the buffer received is a valid one
|
||||||
|
public protocol Verifiable {
|
||||||
|
|
||||||
|
/// Verifies that the current value is which the bounds of the buffer, and if
|
||||||
|
/// the current `Value` is aligned properly
|
||||||
|
/// - Parameters:
|
||||||
|
/// - verifier: Verifier that hosts the buffer
|
||||||
|
/// - position: Current position within the buffer
|
||||||
|
/// - type: The type of the object to be verified
|
||||||
|
/// - Throws: Errors coming from `inBuffer` function
|
||||||
|
static func verify<T>(
|
||||||
|
_ verifier: inout Verifier,
|
||||||
|
at position: Int,
|
||||||
|
of type: T.Type) throws where T: Verifiable
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Verifiable {
|
||||||
|
|
||||||
|
/// Verifies if the current range to be read is within the bounds of the buffer,
|
||||||
|
/// and if the range is properly aligned
|
||||||
|
/// - Parameters:
|
||||||
|
/// - verifier: Verifier that hosts the buffer
|
||||||
|
/// - position: Current position within the buffer
|
||||||
|
/// - type: The type of the object to be verified
|
||||||
|
/// - Throws: Erros thrown from `isAligned` & `rangeInBuffer`
|
||||||
|
/// - Returns: a tuple of the start position and the count of objects within the range
|
||||||
|
@discardableResult
|
||||||
|
public static func verifyRange<T>(
|
||||||
|
_ verifier: inout Verifier,
|
||||||
|
at position: Int, of type: T.Type) throws -> (start: Int, count: Int)
|
||||||
|
{
|
||||||
|
let len: UOffset = try verifier.getValue(at: position)
|
||||||
|
let intLen = Int(len)
|
||||||
|
let start = Int(clamping: (position &+ MemoryLayout<Int32>.size).magnitude)
|
||||||
|
try verifier.isAligned(position: start, type: type.self)
|
||||||
|
try verifier.rangeInBuffer(position: start, size: intLen)
|
||||||
|
return (start, intLen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Verifiable where Self: Scalar {
|
||||||
|
|
||||||
|
/// Verifies that the current value is which the bounds of the buffer, and if
|
||||||
|
/// the current `Value` is aligned properly
|
||||||
|
/// - Parameters:
|
||||||
|
/// - verifier: Verifier that hosts the buffer
|
||||||
|
/// - position: Current position within the buffer
|
||||||
|
/// - type: The type of the object to be verified
|
||||||
|
/// - Throws: Errors coming from `inBuffer` function
|
||||||
|
public static func verify<T>(
|
||||||
|
_ verifier: inout Verifier,
|
||||||
|
at position: Int,
|
||||||
|
of type: T.Type) throws where T: Verifiable
|
||||||
|
{
|
||||||
|
try verifier.inBuffer(position: position, of: type.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - ForwardOffset
|
||||||
|
|
||||||
|
/// ForwardOffset is a container to wrap around the Generic type to be verified
|
||||||
|
/// from the flatbuffers object.
|
||||||
|
public enum ForwardOffset<U>: Verifiable where U: Verifiable {
|
||||||
|
|
||||||
|
/// Verifies that the current value is which the bounds of the buffer, and if
|
||||||
|
/// the current `Value` is aligned properly
|
||||||
|
/// - Parameters:
|
||||||
|
/// - verifier: Verifier that hosts the buffer
|
||||||
|
/// - position: Current position within the buffer
|
||||||
|
/// - type: The type of the object to be verified
|
||||||
|
/// - Throws: Errors coming from `inBuffer` function
|
||||||
|
public static func verify<T>(
|
||||||
|
_ verifier: inout Verifier,
|
||||||
|
at position: Int,
|
||||||
|
of type: T.Type) throws where T: Verifiable
|
||||||
|
{
|
||||||
|
let offset: UOffset = try verifier.getValue(at: position)
|
||||||
|
let nextOffset = Int(clamping: (Int(offset) &+ position).magnitude)
|
||||||
|
try U.verify(&verifier, at: nextOffset, of: U.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Vector
|
||||||
|
|
||||||
|
/// Vector is a container to wrap around the Generic type to be verified
|
||||||
|
/// from the flatbuffers object.
|
||||||
|
public enum Vector<U, S>: Verifiable where U: Verifiable, S: Verifiable {
|
||||||
|
|
||||||
|
/// Verifies that the current value is which the bounds of the buffer, and if
|
||||||
|
/// the current `Value` is aligned properly
|
||||||
|
/// - Parameters:
|
||||||
|
/// - verifier: Verifier that hosts the buffer
|
||||||
|
/// - position: Current position within the buffer
|
||||||
|
/// - type: The type of the object to be verified
|
||||||
|
/// - Throws: Errors coming from `inBuffer` function
|
||||||
|
public static func verify<T>(
|
||||||
|
_ verifier: inout Verifier,
|
||||||
|
at position: Int,
|
||||||
|
of type: T.Type) throws where T: Verifiable
|
||||||
|
{
|
||||||
|
/// checks if the next verification type S is equal to U of type forwardOffset
|
||||||
|
/// This had to be done since I couldnt find a solution for duplicate call functions
|
||||||
|
/// A fix will be appreciated
|
||||||
|
if U.self is ForwardOffset<S>.Type {
|
||||||
|
let range = try verifyRange(&verifier, at: position, of: UOffset.self)
|
||||||
|
for index in stride(
|
||||||
|
from: range.start,
|
||||||
|
to: Int(
|
||||||
|
clamping: range
|
||||||
|
.start &+ (range.count &* MemoryLayout<Int32>.size)),
|
||||||
|
by: MemoryLayout<UOffset>.size)
|
||||||
|
{
|
||||||
|
try U.verify(&verifier, at: index, of: U.self)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try S.verifyRange(&verifier, at: position, of: S.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UnionVector
|
||||||
|
|
||||||
|
/// UnionVector is a container to wrap around the Generic type to be verified
|
||||||
|
/// from the flatbuffers object.
|
||||||
|
public enum UnionVector<S> where S: UnionEnum {
|
||||||
|
|
||||||
|
/// Completion handler for the function Verify, that passes the verifier
|
||||||
|
/// enum type and position of union field
|
||||||
|
public typealias Completion = (inout Verifier, S, Int) throws -> Void
|
||||||
|
|
||||||
|
/// Verifies if the current range to be read is within the bounds of the buffer,
|
||||||
|
/// and if the range is properly aligned. It also verifies if the union type is a
|
||||||
|
/// *valid/supported* union type.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - verifier: Verifier that hosts the buffer
|
||||||
|
/// - keyPosition: Current union key position within the buffer
|
||||||
|
/// - fieldPosition: Current union field position within the buffer
|
||||||
|
/// - unionKeyName: Name of key to written if error is presented
|
||||||
|
/// - fieldName: Name of field to written if error is presented
|
||||||
|
/// - completion: Completion is a handler that WILL be called in the generated
|
||||||
|
/// code to verify the actual objects
|
||||||
|
/// - Throws: FlatbuffersErrors
|
||||||
|
public static func verify(
|
||||||
|
_ verifier: inout Verifier,
|
||||||
|
keyPosition: Int,
|
||||||
|
fieldPosition: Int,
|
||||||
|
unionKeyName: String,
|
||||||
|
fieldName: String,
|
||||||
|
completion: @escaping Completion) throws
|
||||||
|
{
|
||||||
|
/// Get offset for union key vectors and offset vectors
|
||||||
|
let keyOffset: UOffset = try verifier.getValue(at: keyPosition)
|
||||||
|
let fieldOffset: UOffset = try verifier.getValue(at: fieldPosition)
|
||||||
|
|
||||||
|
/// Check if values are within the buffer, returns the start position of vectors, and vector counts
|
||||||
|
/// Using &+ is safe since we already verified that the value is within the buffer, where the max is
|
||||||
|
/// going to be 2Gib and swift supports Int64 by default
|
||||||
|
let keysRange = try S.T.verifyRange(
|
||||||
|
&verifier,
|
||||||
|
at: Int(keyOffset) &+ keyPosition,
|
||||||
|
of: S.T.self)
|
||||||
|
let offsetsRange = try UOffset.verifyRange(
|
||||||
|
&verifier,
|
||||||
|
at: Int(fieldOffset) &+ fieldPosition,
|
||||||
|
of: UOffset.self)
|
||||||
|
|
||||||
|
guard keysRange.count == offsetsRange.count else {
|
||||||
|
throw FlatbuffersErrors.unionVectorSize(
|
||||||
|
keyVectorSize: keysRange.count,
|
||||||
|
fieldVectorSize: offsetsRange.count,
|
||||||
|
unionKeyName: unionKeyName,
|
||||||
|
fieldName: fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
/// Iterate over the vector of keys and offsets.
|
||||||
|
while count < keysRange.count {
|
||||||
|
|
||||||
|
/// index of readable enum value in array
|
||||||
|
let keysIndex = MemoryLayout<S.T>.size * count
|
||||||
|
guard let _enum = try S.init(value: verifier._buffer.read(
|
||||||
|
def: S.T.self,
|
||||||
|
position: keysRange.start + keysIndex)) else
|
||||||
|
{
|
||||||
|
throw FlatbuffersErrors.unknownUnionCase
|
||||||
|
}
|
||||||
|
/// index of readable offset value in array
|
||||||
|
let fieldIndex = MemoryLayout<UOffset>.size * count
|
||||||
|
try completion(&verifier, _enum, offsetsRange.start + fieldIndex)
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
238
submodules/TelegramCore/FlatBuffers/Sources/Verifier.swift
Normal file
238
submodules/TelegramCore/FlatBuffers/Sources/Verifier.swift
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Verifier that check if the buffer passed into it is a valid,
|
||||||
|
/// safe, aligned Flatbuffers object since swift read from `unsafeMemory`
|
||||||
|
public struct Verifier {
|
||||||
|
|
||||||
|
/// Flag to check for alignment if true
|
||||||
|
fileprivate let _checkAlignment: Bool
|
||||||
|
/// Storage for all changing values within the verifier
|
||||||
|
private let storage: Storage
|
||||||
|
/// Current verifiable ByteBuffer
|
||||||
|
internal var _buffer: ByteBuffer
|
||||||
|
/// Options for verification
|
||||||
|
internal let _options: VerifierOptions
|
||||||
|
|
||||||
|
/// Current stored capacity within the verifier
|
||||||
|
var capacity: Int {
|
||||||
|
storage.capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current depth of verifier
|
||||||
|
var depth: Int {
|
||||||
|
storage.depth
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current table count
|
||||||
|
var tableCount: Int {
|
||||||
|
storage.tableCount
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Initializer for the verifier
|
||||||
|
/// - Parameters:
|
||||||
|
/// - buffer: Bytebuffer that is required to be verified
|
||||||
|
/// - options: `VerifierOptions` that set the rule for some of the verification done
|
||||||
|
/// - checkAlignment: If alignment check is required to be preformed
|
||||||
|
/// - Throws: `exceedsMaxSizeAllowed` if capacity of the buffer is more than 2GiB
|
||||||
|
public init(
|
||||||
|
buffer: inout ByteBuffer,
|
||||||
|
options: VerifierOptions = .init(),
|
||||||
|
checkAlignment: Bool = true) throws
|
||||||
|
{
|
||||||
|
guard buffer.capacity < FlatBufferMaxSize else {
|
||||||
|
throw FlatbuffersErrors.exceedsMaxSizeAllowed
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffer = buffer
|
||||||
|
_checkAlignment = checkAlignment
|
||||||
|
_options = options
|
||||||
|
storage = Storage(capacity: buffer.capacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets the verifier to initial state
|
||||||
|
public func reset() {
|
||||||
|
storage.depth = 0
|
||||||
|
storage.tableCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the value of type `T` is aligned properly in the buffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - position: Current position
|
||||||
|
/// - type: Type of value to check
|
||||||
|
/// - Throws: `missAlignedPointer` if the pointer is not aligned properly
|
||||||
|
public func isAligned<T>(position: Int, type: T.Type) throws {
|
||||||
|
|
||||||
|
/// If check alignment is false this mutating function doesnt continue
|
||||||
|
if !_checkAlignment { return }
|
||||||
|
|
||||||
|
/// advance pointer to position X
|
||||||
|
let ptr = _buffer._storage.memory.advanced(by: position)
|
||||||
|
/// Check if the pointer is aligned
|
||||||
|
if Int(bitPattern: ptr) & (MemoryLayout<T>.alignment &- 1) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw FlatbuffersErrors.missAlignedPointer(
|
||||||
|
position: position,
|
||||||
|
type: String(describing: T.self))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the value of Size "X" is within the range of the buffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - position: Current position to be read
|
||||||
|
/// - size: `Byte` Size of readable object within the buffer
|
||||||
|
/// - Throws: `outOfBounds` if the value is out of the bounds of the buffer
|
||||||
|
/// and `apparentSizeTooLarge` if the apparent size is bigger than the one specified
|
||||||
|
/// in `VerifierOptions`
|
||||||
|
public func rangeInBuffer(position: Int, size: Int) throws {
|
||||||
|
let end = UInt(clamping: (position &+ size).magnitude)
|
||||||
|
if end > _buffer.capacity {
|
||||||
|
throw FlatbuffersErrors.outOfBounds(position: end, end: storage.capacity)
|
||||||
|
}
|
||||||
|
storage.apparentSize = storage.apparentSize &+ UInt32(size)
|
||||||
|
if storage.apparentSize > _options._maxApparentSize {
|
||||||
|
throw FlatbuffersErrors.apparentSizeTooLarge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates if a value of type `T` is aligned and within the bounds of
|
||||||
|
/// the buffer
|
||||||
|
/// - Parameters:
|
||||||
|
/// - position: Current readable position
|
||||||
|
/// - type: Type of value to check
|
||||||
|
/// - Throws: FlatbuffersErrors
|
||||||
|
public func inBuffer<T>(position: Int, of type: T.Type) throws {
|
||||||
|
try isAligned(position: position, type: type)
|
||||||
|
try rangeInBuffer(position: position, size: MemoryLayout<T>.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visits a table at the current position and validates if the table meets
|
||||||
|
/// the rules specified in the `VerifierOptions`
|
||||||
|
/// - Parameter position: Current position to be read
|
||||||
|
/// - Throws: FlatbuffersErrors
|
||||||
|
/// - Returns: A `TableVerifier` at the current readable table
|
||||||
|
public mutating func visitTable(at position: Int) throws -> TableVerifier {
|
||||||
|
let vtablePosition = try derefOffset(position: position)
|
||||||
|
let vtableLength: VOffset = try getValue(at: vtablePosition)
|
||||||
|
|
||||||
|
let length = Int(vtableLength)
|
||||||
|
try isAligned(
|
||||||
|
position: Int(clamping: (vtablePosition + length).magnitude),
|
||||||
|
type: VOffset.self)
|
||||||
|
try rangeInBuffer(position: vtablePosition, size: length)
|
||||||
|
|
||||||
|
storage.tableCount += 1
|
||||||
|
|
||||||
|
if storage.tableCount > _options._maxTableCount {
|
||||||
|
throw FlatbuffersErrors.maximumTables
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.depth += 1
|
||||||
|
|
||||||
|
if storage.depth > _options._maxDepth {
|
||||||
|
throw FlatbuffersErrors.maximumDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
return TableVerifier(
|
||||||
|
position: position,
|
||||||
|
vtable: vtablePosition,
|
||||||
|
vtableLength: length,
|
||||||
|
verifier: &self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates if a value of type `T` is within the buffer and returns it
|
||||||
|
/// - Parameter position: Current position to be read
|
||||||
|
/// - Throws: `inBuffer` errors
|
||||||
|
/// - Returns: a value of type `T` usually a `VTable` or a table offset
|
||||||
|
internal func getValue<T>(at position: Int) throws -> T {
|
||||||
|
try inBuffer(position: position, of: T.self)
|
||||||
|
return _buffer.read(def: T.self, position: position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// derefrences an offset within a vtable to get the position of the field
|
||||||
|
/// in the bytebuffer
|
||||||
|
/// - Parameter position: Current readable position
|
||||||
|
/// - Throws: `inBuffer` errors & `signedOffsetOutOfBounds`
|
||||||
|
/// - Returns: Current readable position for a field
|
||||||
|
@inline(__always)
|
||||||
|
internal func derefOffset(position: Int) throws -> Int {
|
||||||
|
try inBuffer(position: position, of: Int32.self)
|
||||||
|
|
||||||
|
let offset = _buffer.read(def: Int32.self, position: position)
|
||||||
|
// switching to int32 since swift's default Int is int64
|
||||||
|
// this should be safe since we already checked if its within
|
||||||
|
// the buffer
|
||||||
|
let _int32Position = UInt32(position)
|
||||||
|
|
||||||
|
let reportedOverflow: (partialValue: UInt32, overflow: Bool)
|
||||||
|
if offset > 0 {
|
||||||
|
reportedOverflow = _int32Position
|
||||||
|
.subtractingReportingOverflow(offset.magnitude)
|
||||||
|
} else {
|
||||||
|
reportedOverflow = _int32Position
|
||||||
|
.addingReportingOverflow(offset.magnitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// since `subtractingReportingOverflow` & `addingReportingOverflow` returns true,
|
||||||
|
/// if there is overflow we return failure
|
||||||
|
if reportedOverflow.overflow || reportedOverflow.partialValue > _buffer
|
||||||
|
.capacity
|
||||||
|
{
|
||||||
|
throw FlatbuffersErrors.signedOffsetOutOfBounds(
|
||||||
|
offset: Int(offset),
|
||||||
|
position: position)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Int(reportedOverflow.partialValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// finishes the current iteration of verification on an object
|
||||||
|
internal func finish() {
|
||||||
|
storage.depth -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@inline(__always)
|
||||||
|
func verify(id: String) throws {
|
||||||
|
let size = MemoryLayout<Int32>.size
|
||||||
|
guard storage.capacity >= (size * 2) else {
|
||||||
|
throw FlatbuffersErrors.bufferDoesntContainID
|
||||||
|
}
|
||||||
|
let str = _buffer.readString(at: size, count: size)
|
||||||
|
if id == str {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw FlatbuffersErrors.bufferIdDidntMatchPassedId
|
||||||
|
}
|
||||||
|
|
||||||
|
final private class Storage {
|
||||||
|
/// Current ApparentSize
|
||||||
|
fileprivate var apparentSize: UOffset = 0
|
||||||
|
/// Amount of tables present within a buffer
|
||||||
|
fileprivate var tableCount = 0
|
||||||
|
/// Capacity of the current buffer
|
||||||
|
fileprivate let capacity: Int
|
||||||
|
/// Current reached depth within the buffer
|
||||||
|
fileprivate var depth = 0
|
||||||
|
|
||||||
|
init(capacity: Int) {
|
||||||
|
self.capacity = capacity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
submodules/TelegramCore/FlatSerialization/BUILD
Normal file
56
submodules/TelegramCore/FlatSerialization/BUILD
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
models = glob([
|
||||||
|
"Models/*.fbs",
|
||||||
|
])
|
||||||
|
|
||||||
|
model_names = [
|
||||||
|
f[7:-4] for f in models
|
||||||
|
]
|
||||||
|
|
||||||
|
generated_models = [ "{}_generated.swift".format(name) for name in model_names ]
|
||||||
|
flatc_input = " ".join([ "$(location Models/{}.fbs)".format(name) for name in model_names ])
|
||||||
|
|
||||||
|
genrule(
|
||||||
|
name = "GenerateModels",
|
||||||
|
srcs = models,
|
||||||
|
tools = [
|
||||||
|
"//third-party/flatc:flatc_bin"
|
||||||
|
],
|
||||||
|
cmd_bash =
|
||||||
|
"""
|
||||||
|
set -ex
|
||||||
|
FLATC="$$(pwd)/$(location //third-party/flatc:flatc_bin)"
|
||||||
|
|
||||||
|
BUILD_DIR="$(RULEDIR)/build"
|
||||||
|
rm -rf "$$BUILD_DIR"
|
||||||
|
mkdir -p "$$BUILD_DIR"
|
||||||
|
|
||||||
|
"$$FLATC" --swift -o "$$BUILD_DIR" {flatc_input}
|
||||||
|
""".format(
|
||||||
|
flatc_input=flatc_input
|
||||||
|
) + "\n" + "\n".join([
|
||||||
|
"""
|
||||||
|
cp "$$BUILD_DIR/{name}_generated.swift" "$(location {name}_generated.swift)"
|
||||||
|
""".format(name=name) for name in model_names
|
||||||
|
]),
|
||||||
|
outs = generated_models,
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "FlatSerialization",
|
||||||
|
module_name = "FlatSerialization",
|
||||||
|
srcs = generated_models,
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/TelegramCore/FlatBuffers",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace TelegramCore;
|
||||||
|
|
||||||
|
struct MediaId {
|
||||||
|
namespace: int;
|
||||||
|
id: int64;
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace TelegramCore;
|
||||||
|
|
||||||
|
struct PixelDimensions {
|
||||||
|
width: int;
|
||||||
|
height: int;
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
include "MediaId.fbs";
|
||||||
|
include "VideoThumbnail.fbs";
|
||||||
|
|
||||||
|
namespace TelegramCore;
|
||||||
|
|
||||||
|
table TelegramMediaFile {
|
||||||
|
id: MediaId;
|
||||||
|
videoThumbnails: [VideoThumbnail];
|
||||||
|
}
|
||||||
|
|
||||||
|
root_type TelegramMediaFile;
|
@ -0,0 +1,7 @@
|
|||||||
|
include "PixelDimensions.fbs";
|
||||||
|
|
||||||
|
namespace TelegramCore;
|
||||||
|
|
||||||
|
struct VideoThumbnail {
|
||||||
|
dimensions: PixelDimensions;
|
||||||
|
}
|
@ -1005,8 +1005,11 @@ private final class CallSessionManagerContext {
|
|||||||
if let internalId = self.contextIdByStableId[id] {
|
if let internalId = self.contextIdByStableId[id] {
|
||||||
if let context = self.contexts[internalId] {
|
if let context = self.contexts[internalId] {
|
||||||
switch context.state {
|
switch context.state {
|
||||||
case .accepting, .active, .dropping, .requesting, .ringing, .terminated, .requested, .switchedToConference:
|
case .accepting, .dropping, .requesting, .ringing, .terminated, .requested, .switchedToConference:
|
||||||
break
|
break
|
||||||
|
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _):
|
||||||
|
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init))
|
||||||
|
self.contextUpdated(internalId: internalId)
|
||||||
case let .awaitingConfirmation(_, accessHash, gAHash, b, config):
|
case let .awaitingConfirmation(_, accessHash, gAHash, b, config):
|
||||||
if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) {
|
if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) {
|
||||||
if keyFingerprint == calculatedKeyId {
|
if keyFingerprint == calculatedKeyId {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Postbox
|
import Postbox
|
||||||
|
|
||||||
private let typeFileName: Int32 = 0
|
private let typeFileName: Int32 = 0
|
||||||
|
@ -1117,10 +1117,15 @@ public final class GroupCallParticipantsContext {
|
|||||||
|
|
||||||
var pendingMuteStateChanges: [PeerId: MuteStateChange] = [:]
|
var pendingMuteStateChanges: [PeerId: MuteStateChange] = [:]
|
||||||
|
|
||||||
|
var hasLocalVideo: PeerId? = nil
|
||||||
|
|
||||||
var isEmpty: Bool {
|
var isEmpty: Bool {
|
||||||
if !self.pendingMuteStateChanges.isEmpty {
|
if !self.pendingMuteStateChanges.isEmpty {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if self.hasLocalVideo != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1254,6 +1259,12 @@ public final class GroupCallParticipantsContext {
|
|||||||
publicState.participants[i].raiseHandRating = nil
|
publicState.participants[i].raiseHandRating = nil
|
||||||
sortAgain = true
|
sortAgain = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let hasLocalVideoPeerId = state.overlayState.hasLocalVideo, hasLocalVideoPeerId == publicState.participants[i].peer.id {
|
||||||
|
if publicState.participants[i].videoDescription == nil {
|
||||||
|
publicState.participants[i].videoDescription = GroupCallParticipantsContext.Participant.VideoDescription(endpointId: "_local", ssrcGroups: [], audioSsrc: nil, isPaused: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if sortAgain {
|
if sortAgain {
|
||||||
publicState.participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: publicState.sortAscending) })
|
publicState.participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: publicState.sortAscending) })
|
||||||
@ -1943,6 +1954,10 @@ public final class GroupCallParticipantsContext {
|
|||||||
self.localIsVideoPaused = isVideoPaused
|
self.localIsVideoPaused = isVideoPaused
|
||||||
self.localIsPresentationPaused = isPresentationPaused
|
self.localIsPresentationPaused = isPresentationPaused
|
||||||
|
|
||||||
|
if let isVideoMuted {
|
||||||
|
self.stateValue.overlayState.hasLocalVideo = isVideoMuted ? nil : peerId
|
||||||
|
}
|
||||||
|
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
let account = self.account
|
let account = self.account
|
||||||
|
@ -86,7 +86,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, insets: UIEdgeInsets, minWidth: CGFloat, controlsHidden: Bool, displayClose: Bool, strings: PresentationStrings, buttons: [Button], notices: [Notice], transition: ComponentTransition) -> CGFloat {
|
func update(size: CGSize, insets: UIEdgeInsets, minWidth: CGFloat, controlsHidden: Bool, displayClose: Bool, strings: PresentationStrings, buttons: [Button], notices: [Notice], isAnimatedOutToGroupCall: Bool, transition: ComponentTransition) -> CGFloat {
|
||||||
self.buttons = buttons
|
self.buttons = buttons
|
||||||
|
|
||||||
let buttonSize: CGFloat = 56.0
|
let buttonSize: CGFloat = 56.0
|
||||||
@ -95,7 +95,9 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
let buttonNoticeSpacing: CGFloat = 16.0
|
let buttonNoticeSpacing: CGFloat = 16.0
|
||||||
let controlsHiddenNoticeSpacing: CGFloat = 0.0
|
let controlsHiddenNoticeSpacing: CGFloat = 0.0
|
||||||
var nextNoticeY: CGFloat
|
var nextNoticeY: CGFloat
|
||||||
if controlsHidden {
|
if isAnimatedOutToGroupCall {
|
||||||
|
nextNoticeY = size.height + 4.0
|
||||||
|
} else if controlsHidden {
|
||||||
nextNoticeY = size.height - insets.bottom - 4.0
|
nextNoticeY = size.height - insets.bottom - 4.0
|
||||||
} else {
|
} else {
|
||||||
nextNoticeY = size.height - insets.bottom - 52.0 - buttonSize - buttonNoticeSpacing
|
nextNoticeY = size.height - insets.bottom - 52.0 - buttonSize - buttonNoticeSpacing
|
||||||
@ -130,9 +132,11 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let noticeSize = noticeView.update(icon: notice.icon, text: notice.text, constrainedWidth: size.width - insets.left * 2.0 - 16.0 * 2.0, transition: noticeTransition)
|
let noticeSize = noticeView.update(icon: notice.icon, text: notice.text, constrainedWidth: size.width - insets.left * 2.0 - 16.0 * 2.0, transition: noticeTransition)
|
||||||
let noticeFrame = CGRect(origin: CGPoint(x: floor((size.width - noticeSize.width) * 0.5), y: nextNoticeY - noticeSize.height), size: noticeSize)
|
let noticeFrame = CGRect(origin: CGPoint(x: floor((size.width - noticeSize.width) * 0.5), y: isAnimatedOutToGroupCall ? nextNoticeY : (nextNoticeY - noticeSize.height)), size: noticeSize)
|
||||||
noticesHeight += noticeSize.height
|
noticesHeight += noticeSize.height
|
||||||
nextNoticeY -= noticeSize.height + noticeSpacing
|
if !isAnimatedOutToGroupCall {
|
||||||
|
nextNoticeY -= noticeSize.height + noticeSpacing
|
||||||
|
}
|
||||||
|
|
||||||
noticeTransition.setFrame(view: noticeView, frame: noticeFrame)
|
noticeTransition.setFrame(view: noticeView, frame: noticeFrame)
|
||||||
if animateIn, !transition.animation.isImmediate {
|
if animateIn, !transition.animation.isImmediate {
|
||||||
@ -142,6 +146,9 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
if noticesHeight != 0.0 {
|
if noticesHeight != 0.0 {
|
||||||
noticesHeight += 5.0
|
noticesHeight += 5.0
|
||||||
}
|
}
|
||||||
|
if isAnimatedOutToGroupCall {
|
||||||
|
noticesHeight = 0.0
|
||||||
|
}
|
||||||
var removedNoticeIds: [AnyHashable] = []
|
var removedNoticeIds: [AnyHashable] = []
|
||||||
for (id, noticeView) in self.noticeViews {
|
for (id, noticeView) in self.noticeViews {
|
||||||
if !validNoticeIds.contains(id) {
|
if !validNoticeIds.contains(id) {
|
||||||
@ -161,7 +168,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
|
|
||||||
let buttonY: CGFloat
|
let buttonY: CGFloat
|
||||||
let resultHeight: CGFloat
|
let resultHeight: CGFloat
|
||||||
if controlsHidden {
|
if controlsHidden || isAnimatedOutToGroupCall {
|
||||||
buttonY = size.height + 12.0
|
buttonY = size.height + 12.0
|
||||||
resultHeight = insets.bottom + 4.0 + noticesHeight
|
resultHeight = insets.bottom + 4.0 + noticesHeight
|
||||||
} else {
|
} else {
|
||||||
|
@ -29,7 +29,7 @@ public func resolveCallVideoRotationAngle(angle: Float, followsDeviceOrientation
|
|||||||
return (angle + interfaceAngle).truncatingRemainder(dividingBy: Float.pi * 2.0)
|
return (angle + interfaceAngle).truncatingRemainder(dividingBy: Float.pi * 2.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class VideoContainerLayer: SimpleLayer {
|
final class VideoContainerLayer: SimpleLayer {
|
||||||
let contentsLayer: SimpleLayer
|
let contentsLayer: SimpleLayer
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
@ -129,11 +129,16 @@ final class VideoContainerView: HighlightTrackingButton {
|
|||||||
|
|
||||||
let key: Key
|
let key: Key
|
||||||
|
|
||||||
private let videoContainerLayer: VideoContainerLayer
|
let videoContainerLayer: VideoContainerLayer
|
||||||
|
var videoContainerLayerTaken: Bool = false
|
||||||
|
|
||||||
private var videoLayer: PrivateCallVideoLayer
|
private var videoLayer: PrivateCallVideoLayer
|
||||||
private var disappearingVideoLayer: DisappearingVideo?
|
private var disappearingVideoLayer: DisappearingVideo?
|
||||||
|
|
||||||
|
var currentVideoOutput: VideoSource.Output? {
|
||||||
|
return self.videoLayer.video
|
||||||
|
}
|
||||||
|
|
||||||
let blurredContainerLayer: SimpleLayer
|
let blurredContainerLayer: SimpleLayer
|
||||||
|
|
||||||
private let shadowContainer: SimpleLayer
|
private let shadowContainer: SimpleLayer
|
||||||
@ -245,7 +250,7 @@ final class VideoContainerView: HighlightTrackingButton {
|
|||||||
self.layer.addSublayer(self.shadowContainer)
|
self.layer.addSublayer(self.shadowContainer)
|
||||||
|
|
||||||
self.highligthedChanged = { [weak self] highlighted in
|
self.highligthedChanged = { [weak self] highlighted in
|
||||||
guard let self, let params = self.params, !self.videoContainerLayer.bounds.isEmpty else {
|
guard let self, let params = self.params, !self.videoContainerLayer.bounds.isEmpty, !self.videoContainerLayerTaken else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var highlightedState = false
|
var highlightedState = false
|
||||||
@ -316,6 +321,10 @@ final class VideoContainerView: HighlightTrackingButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||||
|
if self.videoContainerLayerTaken {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .began, .changed:
|
case .began, .changed:
|
||||||
self.dragVelocity = CGPoint()
|
self.dragVelocity = CGPoint()
|
||||||
@ -549,6 +558,9 @@ final class VideoContainerView: HighlightTrackingButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func update(previousParams: Params?, params: Params, transition: ComponentTransition) {
|
private func update(previousParams: Params?, params: Params, transition: ComponentTransition) {
|
||||||
|
if self.videoContainerLayerTaken {
|
||||||
|
return
|
||||||
|
}
|
||||||
guard let videoMetrics = self.videoMetrics else {
|
guard let videoMetrics = self.videoMetrics else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -199,6 +199,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
|
|
||||||
private var isUpdating: Bool = false
|
private var isUpdating: Bool = false
|
||||||
|
|
||||||
|
private var isAnimatedOutToGroupCall: Bool = false
|
||||||
|
private var animateOutToGroupCallCompletion: (() -> Void)?
|
||||||
|
|
||||||
private var canAnimateAudioLevel: Bool = false
|
private var canAnimateAudioLevel: Bool = false
|
||||||
private var displayEmojiTooltip: Bool = false
|
private var displayEmojiTooltip: Bool = false
|
||||||
private var isEmojiKeyExpanded: Bool = false
|
private var isEmojiKeyExpanded: Bool = false
|
||||||
@ -233,8 +236,6 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
private var pipVideoCallViewController: UIViewController?
|
private var pipVideoCallViewController: UIViewController?
|
||||||
private var pipController: AVPictureInPictureController?
|
private var pipController: AVPictureInPictureController?
|
||||||
|
|
||||||
private var snowEffectView: SnowEffectView?
|
|
||||||
|
|
||||||
public override init(frame: CGRect) {
|
public override init(frame: CGRect) {
|
||||||
self.overlayContentsView = UIView()
|
self.overlayContentsView = UIView()
|
||||||
self.overlayContentsView.isUserInteractionEnabled = false
|
self.overlayContentsView.isUserInteractionEnabled = false
|
||||||
@ -489,6 +490,32 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func animateOutToGroupChat(completion: @escaping () -> Void) {
|
||||||
|
self.isAnimatedOutToGroupCall = true
|
||||||
|
self.animateOutToGroupCallCompletion = completion
|
||||||
|
self.update(transition: .easeInOut(duration: 0.25))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func takeIncomingVideoLayer() -> (CALayer, VideoSource.Output?)? {
|
||||||
|
var remoteVideoContainerKey: VideoContainerView.Key?
|
||||||
|
if self.swapLocalAndRemoteVideo {
|
||||||
|
if let _ = self.activeRemoteVideoSource {
|
||||||
|
remoteVideoContainerKey = .foreground
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let _ = self.activeRemoteVideoSource {
|
||||||
|
remoteVideoContainerKey = .background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let remoteVideoContainerKey, let videoContainerView = self.videoContainerViews.first(where: { $0.key == remoteVideoContainerKey }) {
|
||||||
|
videoContainerView.videoContainerLayerTaken = true
|
||||||
|
return (videoContainerView.videoContainerLayer, videoContainerView.currentVideoOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
public func update(size: CGSize, insets: UIEdgeInsets, interfaceOrientation: UIInterfaceOrientation, screenCornerRadius: CGFloat, state: State, transition: ComponentTransition) {
|
public func update(size: CGSize, insets: UIEdgeInsets, interfaceOrientation: UIInterfaceOrientation, screenCornerRadius: CGFloat, state: State, transition: ComponentTransition) {
|
||||||
let params = Params(size: size, insets: insets, interfaceOrientation: interfaceOrientation, screenCornerRadius: screenCornerRadius, state: state)
|
let params = Params(size: size, insets: insets, interfaceOrientation: interfaceOrientation, screenCornerRadius: screenCornerRadius, state: state)
|
||||||
if self.params == params {
|
if self.params == params {
|
||||||
@ -717,6 +744,16 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
self.backgroundLayer.update(stateIndex: backgroundStateIndex, isEnergySavingEnabled: params.state.isEnergySavingEnabled, transition: transition)
|
self.backgroundLayer.update(stateIndex: backgroundStateIndex, isEnergySavingEnabled: params.state.isEnergySavingEnabled, transition: transition)
|
||||||
|
|
||||||
|
genericAlphaTransition.setAlpha(layer: self.backgroundLayer, alpha: self.isAnimatedOutToGroupCall ? 0.0 : 1.0, completion: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let animateOutToGroupCallCompletion = self.animateOutToGroupCallCompletion {
|
||||||
|
self.animateOutToGroupCallCompletion = nil
|
||||||
|
animateOutToGroupCallCompletion()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
transition.setFrame(view: self.buttonGroupView, frame: CGRect(origin: CGPoint(), size: params.size))
|
transition.setFrame(view: self.buttonGroupView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||||
|
|
||||||
var isVideoButtonEnabled = false
|
var isVideoButtonEnabled = false
|
||||||
@ -793,7 +830,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}*/
|
}*/
|
||||||
let displayClose = false
|
let displayClose = false
|
||||||
|
|
||||||
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, minWidth: wideContentWidth, controlsHidden: currentAreControlsHidden, displayClose: displayClose, strings: params.state.strings, buttons: buttons, notices: notices, transition: transition)
|
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, minWidth: wideContentWidth, controlsHidden: currentAreControlsHidden, displayClose: displayClose, strings: params.state.strings, buttons: buttons, notices: notices, isAnimatedOutToGroupCall: self.isAnimatedOutToGroupCall, transition: transition)
|
||||||
|
|
||||||
var expandedEmojiKeyRect: CGRect?
|
var expandedEmojiKeyRect: CGRect?
|
||||||
if self.isEmojiKeyExpanded {
|
if self.isEmojiKeyExpanded {
|
||||||
@ -836,7 +873,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
emojiExpandedInfoTransition.setPosition(view: emojiExpandedInfoView, position: CGPoint(x: emojiExpandedInfoFrame.minX + emojiExpandedInfoView.layer.anchorPoint.x * emojiExpandedInfoFrame.width, y: emojiExpandedInfoFrame.minY + emojiExpandedInfoView.layer.anchorPoint.y * emojiExpandedInfoFrame.height))
|
emojiExpandedInfoTransition.setPosition(view: emojiExpandedInfoView, position: CGPoint(x: emojiExpandedInfoFrame.minX + emojiExpandedInfoView.layer.anchorPoint.x * emojiExpandedInfoFrame.width, y: emojiExpandedInfoFrame.minY + emojiExpandedInfoView.layer.anchorPoint.y * emojiExpandedInfoFrame.height))
|
||||||
emojiExpandedInfoTransition.setBounds(view: emojiExpandedInfoView, bounds: CGRect(origin: CGPoint(), size: emojiExpandedInfoFrame.size))
|
emojiExpandedInfoTransition.setBounds(view: emojiExpandedInfoView, bounds: CGRect(origin: CGPoint(), size: emojiExpandedInfoFrame.size))
|
||||||
|
|
||||||
alphaTransition.setAlpha(view: emojiExpandedInfoView, alpha: 1.0)
|
alphaTransition.setAlpha(view: emojiExpandedInfoView, alpha: self.isAnimatedOutToGroupCall ? 0.0 : 1.0)
|
||||||
transition.setScale(view: emojiExpandedInfoView, scale: 1.0)
|
transition.setScale(view: emojiExpandedInfoView, scale: 1.0)
|
||||||
|
|
||||||
expandedEmojiKeyRect = emojiExpandedInfoFrame
|
expandedEmojiKeyRect = emojiExpandedInfoFrame
|
||||||
@ -868,7 +905,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
let backButtonFrame = CGRect(origin: CGPoint(x: params.insets.left + 10.0, y: backButtonY), size: backButtonSize)
|
let backButtonFrame = CGRect(origin: CGPoint(x: params.insets.left + 10.0, y: backButtonY), size: backButtonSize)
|
||||||
transition.setFrame(view: self.backButtonView, frame: backButtonFrame)
|
transition.setFrame(view: self.backButtonView, frame: backButtonFrame)
|
||||||
transition.setAlpha(view: self.backButtonView, alpha: currentAreControlsHidden ? 0.0 : 1.0)
|
genericAlphaTransition.setAlpha(view: self.backButtonView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0)
|
||||||
|
|
||||||
if case let .active(activeState) = params.state.lifecycleState {
|
if case let .active(activeState) = params.state.lifecycleState {
|
||||||
let emojiView: KeyEmojiView
|
let emojiView: KeyEmojiView
|
||||||
@ -886,9 +923,13 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !self.isEmojiKeyExpanded {
|
if !self.isEmojiKeyExpanded {
|
||||||
|
#if DEBUG
|
||||||
|
self.conferenceAddParticipant?()
|
||||||
|
#else
|
||||||
self.isEmojiKeyExpanded = true
|
self.isEmojiKeyExpanded = true
|
||||||
self.displayEmojiTooltip = false
|
self.displayEmojiTooltip = false
|
||||||
self.update(transition: .spring(duration: 0.4))
|
self.update(transition: .spring(duration: 0.4))
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -915,6 +956,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
emojiTransition.setPosition(view: emojiView, position: emojiViewFrame.center)
|
emojiTransition.setPosition(view: emojiView, position: emojiViewFrame.center)
|
||||||
}
|
}
|
||||||
emojiTransition.setBounds(view: emojiView, bounds: CGRect(origin: CGPoint(), size: emojiViewFrame.size))
|
emojiTransition.setBounds(view: emojiView, bounds: CGRect(origin: CGPoint(), size: emojiViewFrame.size))
|
||||||
|
if self.isAnimatedOutToGroupCall {
|
||||||
|
emojiAlphaTransition.setAlpha(view: emojiView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
if let emojiTooltipView = self.emojiTooltipView {
|
if let emojiTooltipView = self.emojiTooltipView {
|
||||||
self.emojiTooltipView = nil
|
self.emojiTooltipView = nil
|
||||||
@ -940,7 +984,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
emojiTransition.setPosition(view: emojiView, position: emojiViewFrame.center)
|
emojiTransition.setPosition(view: emojiView, position: emojiViewFrame.center)
|
||||||
}
|
}
|
||||||
emojiTransition.setBounds(view: emojiView, bounds: CGRect(origin: CGPoint(), size: emojiViewFrame.size))
|
emojiTransition.setBounds(view: emojiView, bounds: CGRect(origin: CGPoint(), size: emojiViewFrame.size))
|
||||||
emojiAlphaTransition.setAlpha(view: emojiView, alpha: currentAreControlsHidden ? 0.0 : 1.0)
|
emojiAlphaTransition.setAlpha(view: emojiView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0)
|
||||||
|
|
||||||
if self.displayEmojiTooltip {
|
if self.displayEmojiTooltip {
|
||||||
let emojiTooltipView: EmojiTooltipView
|
let emojiTooltipView: EmojiTooltipView
|
||||||
@ -1261,6 +1305,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
genericAlphaTransition.setAlpha(layer: self.avatarTransformLayer, alpha: self.isAnimatedOutToGroupCall ? 0.0 : 1.0)
|
||||||
|
genericAlphaTransition.setAlpha(layer: self.blobTransformLayer, alpha: self.isAnimatedOutToGroupCall ? 0.0 : 1.0)
|
||||||
|
|
||||||
let titleSize = self.titleView.update(
|
let titleSize = self.titleView.update(
|
||||||
string: titleString,
|
string: titleString,
|
||||||
fontSize: !havePrimaryVideo ? 28.0 : 17.0,
|
fontSize: !havePrimaryVideo ? 28.0 : 17.0,
|
||||||
@ -1335,7 +1382,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
size: titleSize
|
size: titleSize
|
||||||
)
|
)
|
||||||
transition.setFrame(view: self.titleView, frame: titleFrame)
|
transition.setFrame(view: self.titleView, frame: titleFrame)
|
||||||
genericAlphaTransition.setAlpha(view: self.titleView, alpha: currentAreControlsHidden ? 0.0 : 1.0)
|
genericAlphaTransition.setAlpha(view: self.titleView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0)
|
||||||
|
|
||||||
let statusFrame = CGRect(
|
let statusFrame = CGRect(
|
||||||
origin: CGPoint(
|
origin: CGPoint(
|
||||||
@ -1354,7 +1401,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
transition.setFrame(view: self.statusView, frame: statusFrame)
|
transition.setFrame(view: self.statusView, frame: statusFrame)
|
||||||
genericAlphaTransition.setAlpha(view: self.statusView, alpha: currentAreControlsHidden ? 0.0 : 1.0)
|
genericAlphaTransition.setAlpha(view: self.statusView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .active(activeState) = params.state.lifecycleState, activeState.signalInfo.quality <= 0.2, !self.isEmojiKeyExpanded, (!self.displayEmojiTooltip || !havePrimaryVideo) {
|
if case let .active(activeState) = params.state.lifecycleState, activeState.signalInfo.quality <= 0.2, !self.isEmojiKeyExpanded, (!self.displayEmojiTooltip || !havePrimaryVideo) {
|
||||||
@ -1380,11 +1427,11 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
ComponentTransition.immediate.setScale(view: weakSignalView, scale: 0.001)
|
ComponentTransition.immediate.setScale(view: weakSignalView, scale: 0.001)
|
||||||
weakSignalView.alpha = 0.0
|
weakSignalView.alpha = 0.0
|
||||||
transition.setScaleWithSpring(view: weakSignalView, scale: 1.0)
|
transition.setScaleWithSpring(view: weakSignalView, scale: 1.0)
|
||||||
transition.setAlpha(view: weakSignalView, alpha: 1.0)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
transition.setFrame(view: weakSignalView, frame: weakSignalFrame)
|
transition.setFrame(view: weakSignalView, frame: weakSignalFrame)
|
||||||
}
|
}
|
||||||
|
transition.setAlpha(view: weakSignalView, alpha: self.isAnimatedOutToGroupCall ? 0.0 : 1.0)
|
||||||
} else {
|
} else {
|
||||||
if let weakSignalView = self.weakSignalView {
|
if let weakSignalView = self.weakSignalView {
|
||||||
self.weakSignalView = nil
|
self.weakSignalView = nil
|
||||||
@ -1396,55 +1443,3 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class SnowEffectView: UIView {
|
|
||||||
private let particlesLayer: CAEmitterLayer
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
let particlesLayer = CAEmitterLayer()
|
|
||||||
self.particlesLayer = particlesLayer
|
|
||||||
self.particlesLayer.backgroundColor = nil
|
|
||||||
self.particlesLayer.isOpaque = false
|
|
||||||
|
|
||||||
particlesLayer.emitterShape = .circle
|
|
||||||
particlesLayer.emitterMode = .surface
|
|
||||||
particlesLayer.renderMode = .oldestLast
|
|
||||||
|
|
||||||
let image1 = UIImage(named: "Call/Snow")?.cgImage
|
|
||||||
|
|
||||||
let cell1 = CAEmitterCell()
|
|
||||||
cell1.contents = image1
|
|
||||||
cell1.name = "Snow"
|
|
||||||
cell1.birthRate = 92.0
|
|
||||||
cell1.lifetime = 20.0
|
|
||||||
cell1.velocity = 59.0
|
|
||||||
cell1.velocityRange = -15.0
|
|
||||||
cell1.xAcceleration = 5.0
|
|
||||||
cell1.yAcceleration = 40.0
|
|
||||||
cell1.emissionRange = 90.0 * (.pi / 180.0)
|
|
||||||
cell1.spin = -28.6 * (.pi / 180.0)
|
|
||||||
cell1.spinRange = 57.2 * (.pi / 180.0)
|
|
||||||
cell1.scale = 0.06
|
|
||||||
cell1.scaleRange = 0.3
|
|
||||||
cell1.color = UIColor(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 1.0).cgColor
|
|
||||||
|
|
||||||
particlesLayer.emitterCells = [cell1]
|
|
||||||
|
|
||||||
super.init(frame: frame)
|
|
||||||
|
|
||||||
self.layer.addSublayer(particlesLayer)
|
|
||||||
self.clipsToBounds = true
|
|
||||||
self.backgroundColor = nil
|
|
||||||
self.isOpaque = false
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(size: CGSize) {
|
|
||||||
self.particlesLayer.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
|
|
||||||
self.particlesLayer.emitterSize = CGSize(width: size.width * 3.0, height: size.height * 2.0)
|
|
||||||
self.particlesLayer.emitterPosition = CGPoint(x: size.width * 0.5, y: -325.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -176,6 +176,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
private let callState = Promise<PresentationCallState?>(nil)
|
private let callState = Promise<PresentationCallState?>(nil)
|
||||||
private var awaitingCallConnectionDisposable: Disposable?
|
private var awaitingCallConnectionDisposable: Disposable?
|
||||||
private var callPeerDisposable: Disposable?
|
private var callPeerDisposable: Disposable?
|
||||||
|
private var callIsConferenceDisposable: Disposable?
|
||||||
|
|
||||||
private var groupCallController: VoiceChatController?
|
private var groupCallController: VoiceChatController?
|
||||||
public var currentGroupCallController: ViewController? {
|
public var currentGroupCallController: ViewController? {
|
||||||
@ -811,18 +812,41 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
self.callController?.dismiss()
|
self.callController?.dismiss()
|
||||||
self.callController = nil
|
self.callController = nil
|
||||||
self.hasOngoingCall.set(false)
|
self.hasOngoingCall.set(false)
|
||||||
|
self.callState.set(.single(nil))
|
||||||
|
|
||||||
self.notificationController?.setBlocking(nil)
|
self.notificationController?.setBlocking(nil)
|
||||||
|
|
||||||
self.callPeerDisposable?.dispose()
|
self.callPeerDisposable?.dispose()
|
||||||
self.callPeerDisposable = nil
|
self.callPeerDisposable = nil
|
||||||
|
self.callIsConferenceDisposable?.dispose()
|
||||||
|
self.callIsConferenceDisposable = nil
|
||||||
|
|
||||||
if let call {
|
if let call {
|
||||||
self.callState.set(call.state
|
if call.conferenceCall == nil {
|
||||||
|> map(Optional.init))
|
self.callState.set(call.state
|
||||||
|
|> map(Optional.init))
|
||||||
|
}
|
||||||
|
|
||||||
self.hasOngoingCall.set(true)
|
self.hasOngoingCall.set(true)
|
||||||
setNotificationCall(call)
|
setNotificationCall(call)
|
||||||
|
|
||||||
|
self.callIsConferenceDisposable = (call.hasConference
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let call = self.call else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let callController = self.callController, callController.call === call else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if call.conferenceCall != nil {
|
||||||
|
self.callState.set(.single(nil))
|
||||||
|
self.presentControllerWithCurrentCall()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if call.isOutgoing {
|
if call.isOutgoing {
|
||||||
self.presentControllerWithCurrentCall()
|
self.presentControllerWithCurrentCall()
|
||||||
} else {
|
} else {
|
||||||
@ -921,7 +945,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let groupCallController = makeVoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call, initialData: initialData)
|
let groupCallController = makeVoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call, initialData: initialData, sourceCallController: nil)
|
||||||
groupCallController.onViewDidAppear = { [weak strongSelf] in
|
groupCallController.onViewDidAppear = { [weak strongSelf] in
|
||||||
if let strongSelf {
|
if let strongSelf {
|
||||||
strongSelf.hasGroupCallOnScreenPromise.set(true)
|
strongSelf.hasGroupCallOnScreenPromise.set(true)
|
||||||
@ -947,31 +971,34 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let callSignal: Signal<PresentationCall?, NoError> = .single(nil)
|
let callSignal: Signal<(PresentationCall?, PresentationGroupCall?), NoError> = .single((nil, nil))
|
||||||
|> then(
|
|> then(
|
||||||
callManager.currentCallSignal
|
callManager.currentCallSignal
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> mapToSignal { call -> Signal<PresentationCall?, NoError> in
|
|> mapToSignal { call -> Signal<(PresentationCall?, PresentationGroupCall?), NoError> in
|
||||||
guard let call else {
|
guard let call else {
|
||||||
return .single(nil)
|
return .single((nil, nil))
|
||||||
}
|
}
|
||||||
return call.state
|
return combineLatest(call.state, call.hasConference)
|
||||||
|> map { [weak call] state -> PresentationCall? in
|
|> map { [weak call] state, _ -> (PresentationCall?, PresentationGroupCall?) in
|
||||||
guard let call else {
|
guard let call else {
|
||||||
return nil
|
return (nil, nil)
|
||||||
|
}
|
||||||
|
if let conferenceCall = call.conferenceCall {
|
||||||
|
return (nil, conferenceCall)
|
||||||
}
|
}
|
||||||
switch state.state {
|
switch state.state {
|
||||||
case .ringing:
|
case .ringing:
|
||||||
return nil
|
return (nil, nil)
|
||||||
case .terminating, .terminated:
|
case .terminating, .terminated:
|
||||||
return nil
|
return (nil, nil)
|
||||||
default:
|
default:
|
||||||
return call
|
return (call, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||||
return lhs === rhs
|
return lhs.0 === rhs.0 && lhs.1 === rhs.1
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
let groupCallSignal: Signal<PresentationGroupCall?, NoError> = .single(nil)
|
let groupCallSignal: Signal<PresentationGroupCall?, NoError> = .single(nil)
|
||||||
@ -985,8 +1012,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
self.hasGroupCallOnScreenPromise.get()
|
self.hasGroupCallOnScreenPromise.get()
|
||||||
).start(next: { [weak self] call, groupCall, hasGroupCallOnScreen in
|
).start(next: { [weak self] call, groupCall, hasGroupCallOnScreen in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
var (call, conferenceCall) = call
|
||||||
|
var groupCall = groupCall
|
||||||
|
if let conferenceCall {
|
||||||
|
call = nil
|
||||||
|
groupCall = conferenceCall
|
||||||
|
}
|
||||||
|
|
||||||
let statusBarContent: CallStatusBarNodeImpl.Content?
|
let statusBarContent: CallStatusBarNodeImpl.Content?
|
||||||
if let call = call {
|
if let call {
|
||||||
statusBarContent = .call(strongSelf, call.context.account, call)
|
statusBarContent = .call(strongSelf, call.context.account, call)
|
||||||
} else if let groupCall = groupCall, !hasGroupCallOnScreen {
|
} else if let groupCall = groupCall, !hasGroupCallOnScreen {
|
||||||
statusBarContent = .groupCall(strongSelf, groupCall.account, groupCall)
|
statusBarContent = .groupCall(strongSelf, groupCall.account, groupCall)
|
||||||
@ -1179,30 +1213,75 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let currentCallController = self.callController {
|
if let conferenceCall = call.conferenceCall {
|
||||||
if currentCallController.call == .call(call) {
|
if let groupCallController = self.groupCallController {
|
||||||
self.navigateToCurrentCall()
|
if groupCallController.call === conferenceCall {
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
|
groupCallController.dismiss(closing: true, manual: false)
|
||||||
|
self.groupCallController = nil
|
||||||
|
}
|
||||||
|
var transitioniongCallController: CallController?
|
||||||
|
if let callController = self.callController {
|
||||||
|
transitioniongCallController = callController
|
||||||
|
callController.dismissWithoutAnimation()
|
||||||
self.callController = nil
|
self.callController = nil
|
||||||
currentCallController.dismiss()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = (makeVoiceChatControllerInitialData(sharedContext: self, accountContext: conferenceCall.accountContext, call: conferenceCall)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak transitioniongCallController] initialData in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let navigationController = self.mainWindow?.viewController as? NavigationController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let call = self.call, let conferenceCall = call.conferenceCall else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let groupCallController = makeVoiceChatController(sharedContext: self, accountContext: conferenceCall.accountContext, call: conferenceCall, initialData: initialData, sourceCallController: transitioniongCallController)
|
||||||
|
groupCallController.onViewDidAppear = { [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.hasGroupCallOnScreenPromise.set(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupCallController.onViewDidDisappear = { [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.hasGroupCallOnScreenPromise.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupCallController.navigationPresentation = .flatModal
|
||||||
|
groupCallController.parentNavigationController = navigationController
|
||||||
|
self.groupCallController = groupCallController
|
||||||
|
navigationController.pushViewController(groupCallController)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if let currentCallController = self.callController {
|
||||||
|
if currentCallController.call === call {
|
||||||
|
self.navigateToCurrentCall()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
self.callController = nil
|
||||||
|
currentCallController.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mainWindow?.hostView.containerView.endEditing(true)
|
||||||
|
let callController = CallController(sharedContext: self, account: call.context.account, call: call, easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
|
||||||
|
self.callController = callController
|
||||||
|
callController.restoreUIForPictureInPicture = { [weak self, weak callController] completion in
|
||||||
|
guard let self, let callController else {
|
||||||
|
completion(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if callController.window == nil {
|
||||||
|
self.mainWindow?.present(callController, on: .calls)
|
||||||
|
}
|
||||||
|
completion(true)
|
||||||
|
}
|
||||||
|
self.mainWindow?.present(callController, on: .calls)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.mainWindow?.hostView.containerView.endEditing(true)
|
|
||||||
let callController = CallController(sharedContext: self, account: call.context.account, call: .call(call), easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
|
|
||||||
self.callController = callController
|
|
||||||
callController.restoreUIForPictureInPicture = { [weak self, weak callController] completion in
|
|
||||||
guard let self, let callController else {
|
|
||||||
completion(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if callController.window == nil {
|
|
||||||
self.mainWindow?.present(callController, on: .calls)
|
|
||||||
}
|
|
||||||
completion(true)
|
|
||||||
}
|
|
||||||
self.mainWindow?.present(callController, on: .calls)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateNotificationTokensRegistration() {
|
public func updateNotificationTokensRegistration() {
|
||||||
|
@ -19,6 +19,7 @@ swift_library(
|
|||||||
"//submodules/TgVoipWebrtc:TgVoipWebrtc",
|
"//submodules/TgVoipWebrtc:TgVoipWebrtc",
|
||||||
"//submodules/FFMpegBinding",
|
"//submodules/FFMpegBinding",
|
||||||
"//submodules/ManagedFile",
|
"//submodules/ManagedFile",
|
||||||
|
"//submodules/AppBundle",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -7,6 +7,12 @@ import TelegramUIPreferences
|
|||||||
import TgVoip
|
import TgVoip
|
||||||
import TgVoipWebrtc
|
import TgVoipWebrtc
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
import UIKit
|
||||||
|
import AppBundle
|
||||||
|
import Accelerate
|
||||||
|
#endif
|
||||||
|
|
||||||
private func debugUseLegacyVersionForReflectors() -> Bool {
|
private func debugUseLegacyVersionForReflectors() -> Bool {
|
||||||
#if DEBUG && false
|
#if DEBUG && false
|
||||||
return true
|
return true
|
||||||
@ -407,8 +413,179 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
private extension UIImage {
|
||||||
|
@available(iOS 13.0, *)
|
||||||
|
func toBiplanarYUVPixelBuffer() -> CVPixelBuffer? {
|
||||||
|
guard let cgImage = self.cgImage else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
let width = Int(self.size.width * self.scale)
|
||||||
|
let height = Int(self.size.height * self.scale)
|
||||||
|
|
||||||
|
// 1) Create an ARGB8888 vImage buffer from the UIImage (CGImage).
|
||||||
|
// We will first allocate a buffer for ARGB pixels, then use
|
||||||
|
// vImage to copy cgImage → argbBuffer.
|
||||||
|
|
||||||
|
// Each ARGB pixel is 4 bytes
|
||||||
|
let argbBytesPerPixel = 4
|
||||||
|
let argbRowBytes = width * argbBytesPerPixel
|
||||||
|
|
||||||
|
// Allocate contiguous memory for ARGB data
|
||||||
|
let argbData = malloc(argbRowBytes * height)
|
||||||
|
defer {
|
||||||
|
free(argbData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a vImage buffer for ARGB
|
||||||
|
var argbBuffer = vImage_Buffer(
|
||||||
|
data: argbData,
|
||||||
|
height: vImagePixelCount(height),
|
||||||
|
width: vImagePixelCount(width),
|
||||||
|
rowBytes: argbRowBytes
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize the ARGB buffer from our CGImage
|
||||||
|
// This helper function can fail, so check the result:
|
||||||
|
var format = vImage_CGImageFormat(
|
||||||
|
bitsPerComponent: 8,
|
||||||
|
bitsPerPixel: 32,
|
||||||
|
colorSpace: CGColorSpaceCreateDeviceRGB(),
|
||||||
|
bitmapInfo: CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue),
|
||||||
|
renderingIntent: CGColorRenderingIntent.defaultIntent
|
||||||
|
)!
|
||||||
|
|
||||||
|
if vImageBuffer_InitWithCGImage(
|
||||||
|
&argbBuffer,
|
||||||
|
&format,
|
||||||
|
nil,
|
||||||
|
cgImage,
|
||||||
|
vImage_Flags(kvImageNoFlags)
|
||||||
|
) != kvImageNoError {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Create a CVPixelBuffer in YUV 420 (bi-planar) format.
|
||||||
|
// Typically, you’d choose either kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|
||||||
|
// or kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange.
|
||||||
|
|
||||||
|
let pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|
||||||
|
let attrs: [CFString: Any] = [
|
||||||
|
kCVPixelBufferIOSurfacePropertiesKey: [:],
|
||||||
|
// Optionally, specify other attributes if needed.
|
||||||
|
]
|
||||||
|
|
||||||
|
var cvPixelBufferOut: CVPixelBuffer?
|
||||||
|
guard CVPixelBufferCreate(
|
||||||
|
kCFAllocatorDefault,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
pixelFormat,
|
||||||
|
attrs as CFDictionary,
|
||||||
|
&cvPixelBufferOut
|
||||||
|
) == kCVReturnSuccess,
|
||||||
|
let pixelBuffer = cvPixelBufferOut
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Lock the CVPixelBuffer to get direct access to its planes.
|
||||||
|
CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
|
||||||
|
defer {
|
||||||
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plane 0: Y-plane
|
||||||
|
guard let yBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let yPitch = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
|
||||||
|
|
||||||
|
// Plane 1: CbCr-plane
|
||||||
|
guard let cbcrBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let cbcrPitch = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1)
|
||||||
|
|
||||||
|
// 4) Create vImage buffers for each plane.
|
||||||
|
|
||||||
|
// Y plane is full size (width x height)
|
||||||
|
var yBuffer = vImage_Buffer(
|
||||||
|
data: yBaseAddress,
|
||||||
|
height: vImagePixelCount(height),
|
||||||
|
width: vImagePixelCount(width),
|
||||||
|
rowBytes: yPitch
|
||||||
|
)
|
||||||
|
|
||||||
|
// CbCr plane is half height, but each row has interleaved Cb/Cr
|
||||||
|
// so the plane is (width/2) * 2 bytes = width bytes wide, and height/2.
|
||||||
|
var cbcrBuffer = vImage_Buffer(
|
||||||
|
data: cbcrBaseAddress,
|
||||||
|
height: vImagePixelCount(height / 2),
|
||||||
|
width: vImagePixelCount(width),
|
||||||
|
rowBytes: cbcrPitch
|
||||||
|
)
|
||||||
|
|
||||||
|
var info = vImage_ARGBToYpCbCr()
|
||||||
|
var pixelRange = vImage_YpCbCrPixelRange(Yp_bias: 0, CbCr_bias: 128, YpRangeMax: 255, CbCrRangeMax: 255, YpMax: 255, YpMin: 1, CbCrMax: 255, CbCrMin: 0)
|
||||||
|
vImageConvert_ARGBToYpCbCr_GenerateConversion(kvImage_ARGBToYpCbCrMatrix_ITU_R_709_2, &pixelRange, &info, kvImageARGB8888, kvImage420Yp8_Cb8_Cr8, 0)
|
||||||
|
|
||||||
|
let error = vImageConvert_ARGB8888To420Yp8_CbCr8(
|
||||||
|
&argbBuffer,
|
||||||
|
&yBuffer,
|
||||||
|
&cbcrBuffer,
|
||||||
|
&info,
|
||||||
|
nil,
|
||||||
|
UInt32(kvImageDoNotTile)
|
||||||
|
)
|
||||||
|
|
||||||
|
if error != kvImageNoError {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixelBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13.0, *)
|
||||||
|
var cmSampleBuffer: CMSampleBuffer? {
|
||||||
|
guard let pixelBuffer = self.toBiplanarYUVPixelBuffer() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var newSampleBuffer: CMSampleBuffer? = nil
|
||||||
|
|
||||||
|
var timingInfo = CMSampleTimingInfo(
|
||||||
|
duration: CMTimeMake(value: 1, timescale: 30),
|
||||||
|
presentationTimeStamp: CMTimeMake(value: 0, timescale: 30),
|
||||||
|
decodeTimeStamp: CMTimeMake(value: 0, timescale: 30)
|
||||||
|
)
|
||||||
|
|
||||||
|
var videoInfo: CMVideoFormatDescription? = nil
|
||||||
|
CMVideoFormatDescriptionCreateForImageBuffer(allocator: nil, imageBuffer: pixelBuffer, formatDescriptionOut: &videoInfo)
|
||||||
|
guard let videoInfo = videoInfo else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
CMSampleBufferCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: videoInfo, sampleTiming: &timingInfo, sampleBufferOut: &newSampleBuffer)
|
||||||
|
|
||||||
|
if let newSampleBuffer = newSampleBuffer {
|
||||||
|
let attachments = CMSampleBufferGetSampleAttachmentsArray(newSampleBuffer, createIfNecessary: true)! as NSArray
|
||||||
|
let dict = attachments[0] as! NSMutableDictionary
|
||||||
|
|
||||||
|
dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleAttachmentKey_DisplayImmediately as NSString as String)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSampleBuffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
public final class OngoingCallVideoCapturer {
|
public final class OngoingCallVideoCapturer {
|
||||||
internal let impl: OngoingCallThreadLocalContextVideoCapturer
|
internal let impl: OngoingCallThreadLocalContextVideoCapturer
|
||||||
|
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
private var simulatedVideoTimer: Foundation.Timer?
|
||||||
|
#endif
|
||||||
|
|
||||||
private let isActivePromise = ValuePromise<Bool>(true, ignoreRepeated: true)
|
private let isActivePromise = ValuePromise<Bool>(true, ignoreRepeated: true)
|
||||||
public var isActive: Signal<Bool, NoError> {
|
public var isActive: Signal<Bool, NoError> {
|
||||||
@ -419,13 +596,47 @@ public final class OngoingCallVideoCapturer {
|
|||||||
if isCustom {
|
if isCustom {
|
||||||
self.impl = OngoingCallThreadLocalContextVideoCapturer.withExternalSampleBufferProvider()
|
self.impl = OngoingCallThreadLocalContextVideoCapturer.withExternalSampleBufferProvider()
|
||||||
} else {
|
} else {
|
||||||
|
#if targetEnvironment(simulator) && false
|
||||||
|
self.impl = OngoingCallThreadLocalContextVideoCapturer.withExternalSampleBufferProvider()
|
||||||
|
let imageSize = CGSize(width: 600.0, height: 800.0)
|
||||||
|
UIGraphicsBeginImageContextWithOptions(imageSize, true, 1.0)
|
||||||
|
let sourceImage: UIImage?
|
||||||
|
let imagePath = NSTemporaryDirectory() + "frontCameraImage.jpg"
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: imagePath)), let image = UIImage(data: data) {
|
||||||
|
sourceImage = image
|
||||||
|
} else {
|
||||||
|
sourceImage = UIImage(bundleImageName: "Camera/SelfiePlaceholder")!
|
||||||
|
}
|
||||||
|
if let sourceImage {
|
||||||
|
sourceImage.draw(in: CGRect(origin: CGPoint(), size: imageSize))
|
||||||
|
}
|
||||||
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
self.simulatedVideoTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0 / 30.0, repeats: true, block: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
if let image, let sampleBuffer = image.cmSampleBuffer {
|
||||||
|
self.injectSampleBuffer(sampleBuffer, rotation: .up, completion: {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
#else
|
||||||
self.impl = OngoingCallThreadLocalContextVideoCapturer(deviceId: "", keepLandscape: keepLandscape)
|
self.impl = OngoingCallThreadLocalContextVideoCapturer(deviceId: "", keepLandscape: keepLandscape)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
let isActivePromise = self.isActivePromise
|
let isActivePromise = self.isActivePromise
|
||||||
self.impl.setOnIsActiveUpdated({ value in
|
self.impl.setOnIsActiveUpdated({ value in
|
||||||
isActivePromise.set(value)
|
isActivePromise.set(value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
self.simulatedVideoTimer?.invalidate()
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
public func switchVideoInput(isFront: Bool) {
|
public func switchVideoInput(isFront: Bool) {
|
||||||
self.impl.switchVideoInput(isFront ? "" : "back")
|
self.impl.switchVideoInput(isFront ? "" : "back")
|
||||||
|
6
third-party/flatc/BUILD
vendored
6
third-party/flatc/BUILD
vendored
@ -22,14 +22,14 @@ set -x
|
|||||||
pushd "$$BUILD_DIR/flatbuffers-24.12.23"
|
pushd "$$BUILD_DIR/flatbuffers-24.12.23"
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
PATH="$$PATH:$$CMAKE_DIR/cmake-3.23.1-macos-universal/CMake.app/Contents/bin" cmake .. -DCMAKE_BUILD_TYPE=Release"
|
PATH="$$PATH:$$CMAKE_DIR/cmake-3.23.1-macos-universal/CMake.app/Contents/bin" cmake .. -DCMAKE_BUILD_TYPE=Release -DFLATBUFFERS_BUILD_TESTS=0 -DFLATBUFFERS_INSTALL=0 -DFLATBUFFERS_BUILD_FLATLIB=0 -DFLATBUFFERS_STATIC_FLATC=0
|
||||||
make -j $$core_count
|
make -j $$core_count
|
||||||
popd
|
popd
|
||||||
|
|
||||||
tar -cf "$(location flatc.tar)" -C "$$BUILD_DIR/flatbuffers-24.12.23/build" .
|
cp "$$BUILD_DIR/flatbuffers-24.12.23/build/flatc" "$(location flatc_bin)"
|
||||||
""",
|
""",
|
||||||
outs = [
|
outs = [
|
||||||
"flatc.tar",
|
"flatc_bin",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user