AR联机初探+官方项目代码解析

2023-11-05

  学AR也有一小段时间了,今天给大家分享一下如何让两部以上的设备查看到相同的增强现实景象,在这里作者就以苹果官方的示例来进行解析,一定要把代码下载了和文章对照着看,不然会懵。

官方项目代码地址:https://developer.apple.com/documentation/arkit/creating_a_collaborative_session

项目演示

AR联机项目演示

如果这里看不了直接进主页找视频。

需求分析

1.根据视频中所展示的,不同的AR设备处在一个相同环境(可以不连接同一个局域网)

2.当画面出现了其他的AR设备时,在其他的AR设备上会展现一个坐标球。

3.在AR设备中点击可产生一个具有镜面反射效果的一个金属方块实体,并且该实体能实时被其他的AR设备捕捉,不同的AR设备点击产生的实体材质不同。

实现多用户通信需要用到ARKit特有的MultipeerConnectivity网络协议来进行网络通信传输数据,虽然这个示例使用RealityKit绘制图形,但它不使用RealityKit的机制进行网络实体同步。相反,它仅在演示ARKit协作会话所必需的时间内使用RealityKit作为渲染器。

代码解读

本文就只针对核心代码和逻辑步骤来讲解代码,部分基础代码(如定义等)在此不过多赘述,代码中冒出来的函数也先不用纠结,只用知道功能就行不用管怎么实现,最后相信理清所有思路之后再回去看这些代码,整个项目的代码都会弄清楚的。

既然要实现本机和其他设备的数据同步,那么就需要实现正向通信和反向通信(只是个名词,看字面意思就行)通常使用协议MultipeerConnectivity来实现网络传输。

其中我们应当使用三次握手原则,有点类似于ios设备的airdrop(隔空投送)但更先进一些,只需要打开蓝牙或wifi其中一个就行。具体实现就像是存在A和B两个设备(1)A先广播,B进行搜索(2)B搜到之后,会向A发送一个邀请(请求),建立连接(3)A接受之后,会向B发送一个会话(session),若成功则可以互传数据

tips:代码中MCPeerID为自己设备名字,MCSession用于传输数据(文字,文件,图片等)

A先进行广播,B进行搜索,那么就创建一个MCNearbyServiceAdvertiser用来广播,start开始广播,创建一个MCNearbyServiceBrowser用来搜索,start开始搜索,既然是有关通讯的操作,那么就需要写在MutipeerSession文件中

// 1-A先广播, B搜索
        serviceAdvertiser = MCNearbyServiceAdvertiser(peer: myPeerID, discoveryInfo: nil, serviceType: MultipeerSession.serviceType)
        serviceAdvertiser.delegate = self
        serviceAdvertiser.startAdvertisingPeer()
        
//serviceType的值为“ar-collab”,在这里使用的固定值
        serviceBrowser = MCNearbyServiceBrowser(peer: myPeerID, serviceType: MultipeerSession.serviceType)
        serviceBrowser.delegate = self
        serviceBrowser.startBrowsingForPeers() //调用foundPeer的delegate函数

 B搜到之后, 会向A发送邀请(请求), 建立连接
在B的delegate中判断是否搜索到,若搜索到就像A发送请求

extension MultipeerSession: MCNearbyServiceBrowserDelegate {
    
    /// - Tag: FoundPeer
    public func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String: String]?) {
        // 2-B搜到之后, 会向A发送邀请(请求), 建立连接
        let accepted = peerDiscoveredHandler(peerID)
        if accepted {
            browser.invitePeer(peerID, to: session, withContext: nil, timeout: 10)
        }
    }

    public func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
        // 此程序对未邀请的玩家不做处理
    }
    
}

A接收B的加入请求,发送一个会话(session), 可开始互传数据

extension MultipeerSession: MCNearbyServiceAdvertiserDelegate {
    
    /// - Tag: AcceptInvite
    func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID,
                    withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
        // 3-接收别的玩家的加入请求,发送一个会话(session), 可开始互传数据
        invitationHandler(true, self.session)
    }
}

在MCSession的delegate里就是用于传数据的在这里实现正向通信和反向通信,若连接上传一个数据,否则执行别的操作,若搜到别人的手机也要接受别人的数据,具体操作通过闭包实现,闭包的定义在viewcontroller中,之所以用闭包是因为闭包可以携带参数值到处跑。

设置好MCSession就可以搞Viewcontroller了,在viewWillappear或viewDidappear中自定义配置configuration,并启用短距离通讯协议,这里就属于正常配置,不懂的可以看作者另一篇文章或查一查官网ARKit入门的项目https://blog.csdn.net/yueliangmua/article/details/127318214?spm=1001.2014.3001.5501

         arView.session.delegate = self
        
        // 使用自定义配置
        arView.automaticallyConfigureSession = false
        
        setupCoachingOverlay()
        
        // 在世界追踪session中开启合作功能
        // 合作session中,ARKit会定期提供数据(环境+别的玩家的锚点)供联机玩家共享
        // 这些数据需通过网络协议发送出去--这里使用短距离通信协议MultipeerConnectivity
        configuration.isCollaborationEnabled = true
        
        // 环境纹理设为自动(基于图像照明算法,真实地反射周围环境的光线,使虚拟模型更逼真)
        configuration.environmentTexturing = .automatic
        
        // 防止屏幕变暗让体验变差--禁用idle timer避免因屏幕无交互后的系统自动睡眠
        UIApplication.shared.isIdleTimerDisabled = true
        
        arView.session.run(configuration)

这里需要再设置一个观察者使用观察者模式实时检测SessionID的变化,一旦改变就会通知其他连接中的玩家追踪最新ARAnchor,通过sendARSessionIDTo把id传给其他玩家。(这里不懂先往后看)

// 使用观察者模式(key-value observation)实时监测SessionID的变化--类似属性观察者(willset,didset)
        sessionIDObservation = observe(\.arView.session.identifier, options: [.new]) { vc, change in
            print("SessionID变成了: \(change.newValue!)")
            // 一旦改变SessionID后,告诉其他联机玩家,以便他们能够追踪到最新的ARAnchor
            guard let multipeerSession = self.multipeerSession else { return }
            self.sendARSessionIDTo(peers: multipeerSession.connectedPeers)
        }

设置isCollaborationEnabled=true了之后,短距离通信就开启了,遵循了ARSessionDelegate之后会每隔一段时间调用一个方法didOutputCollaborationData而网络传输数据一般是要进行编码的,由于这个data是ios内置的一种数据形式没有遵循从codable协议所以不能转json,需要使用原始的编码形式,并且因通信协议发送数据时要对传输数据的可靠性进行检测故用ARKit提供的proiority属性进行联动,皆是固定用法。

// 一旦开启合作功能,将定期调用此函数
    // 获取各玩家的现实环境信息,以便所有人能看到同样的虚拟物体--data参数,CollaborationData类型
    func session(_ session: ARSession, didOutputCollaborationData data: ARSession.CollaborationData) {
        guard let multipeerSession = multipeerSession else { return }
        if !multipeerSession.connectedPeers.isEmpty {
            //数据需网络传输,故首先得编码
            //因CollaborationData未遵循Codable协议,遵循的是旧的NSCoding协议,故需用旧方法
            
            guard let encodedData = try? NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true)
                else { fatalError("collaboration data编码失败") }
            // 因MultipeerConnectivity通信协议发送数据时需指定数据是否可靠,可通过ARKit提供的priority属性进行联动
            let dataIsCritical = data.priority == .critical
            multipeerSession.sendToAllPeers(encodedData, reliably: dataIsCritical)
        } else {
            print("暂未找到新玩家,ARKit之后会再次尝试")
        }
    }

MC网络连接成功后-新玩家加入

给B发送本机(A)的MCPeerID和SessionID数据--MCSession中处理

func peerJoined(_ peer: MCPeerID) {
        messageLabel.displayMessage("一个新玩家想加入游戏,请将双方iPhone并排靠近以面向同一个现实环境", duration: 12.0)
        // 向B提供你的SessionID,以便他们可以跟踪你的锚点
        sendARSessionIDTo(peers: [peer])
    }
    

    ARKit为了知道两个玩家之间的相对位置,它必须要先合并各自的世界地图(相机捕捉到的现实环境)

    合并成功(即所有玩家一开始需把iPhone相机面向同一个地方)后可共享玩家们各自的位置以及创建的锚点

    通过didAddanchors的delegate方法,提供自己的ARParticipantAnchor(自己的iPhone锚),等合并成功后对方即可看到我的坐标球

//2-collaboration开启后会形成ARParticipantAnchor以提供自己的世界位置+玩家们合并世界地图成功后可放置立方体模型
    func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
        
        for anchor in anchors {
            //检测是否是ARParticipantAnchor,若是则判断联机成功
            if let participantAnchor = anchor as? ARParticipantAnchor {
                messageLabel.displayMessage("和别的玩家联机成功", duration: 6.0)
                
                //将虚拟物体(彩色坐标球体)放置在别的玩家iPhone的位置上以显示别的玩家--实时更新位置和方向
                //先添加一个容器anchorEntity
                let anchorEntity = AnchorEntity(anchor: participantAnchor)
                
                //渲染设备的坐标系
                let coordinateSystem = MeshResource.generateCoordinateSystemAxes()
                anchorEntity.addChild(coordinateSystem)
                
                //渲染球体,根据sessionid随机生成颜色
                let color = participantAnchor.sessionIdentifier?.toRandomColor() ?? .white
                let coloredSphere = ModelEntity(
                    mesh: MeshResource.generateSphere(radius: 0.03),
                    materials: [SimpleMaterial(color: color, isMetallic: true)]
                )
                anchorEntity.addChild(coloredSphere)
                
                arView.scene.addAnchor(anchorEntity)
                
            } else if anchor.name == "Anchor for object placement" {
                
                //当成功合并两个玩家的世界数据后,其中一玩家点击屏幕添加虚拟立方体的同时,立方体也同时加进了另一个玩家的现实环境中了
                let anchorEntity = AnchorEntity(anchor: anchor)
                
                let boxLength: Float = 0.05
                let color = anchor.sessionIdentifier?.toRandomColor() ?? .white
                let coloredCube = ModelEntity(
                    mesh: MeshResource.generateBox(size: boxLength),
                    materials: [SimpleMaterial(color: color, isMetallic: true)]
                )
//因为中心点(锚点)位于平面,所以正方体有一半是嵌入到里面的,所以需要向上移动正方体高度的一半
                coloredCube.position = [0, boxLength / 2, 0]
                
                // 放入立方体模型
                anchorEntity.addChild(coloredCube)
                arView.scene.addAnchor(anchorEntity)
            }
        }

至此发送方任务完成,那么接收方如何接收我添加的这些数据呢?接收方任务开始,接收方

此处会接收两种数据:1.系统调用didOutputCollaborationData之后,我们通过MC网络发送的CollaborationData数据 2.通过MC网络手动send的数据(见peerJoined函数)--“SessionID:xxx”,若没有colloaboration则有可能换了一个sessionid即接收第二个数据(因为colloaboration的数据只有连接成功的sessionid才有)所以更新一下sessionid并把旧的数据删除掉。那么接收方的任务也完成。

func receivedData(_ data: Data, from peer: MCPeerID) {
        if let collaborationData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ARSession.CollaborationData.self, from: data) {
            // 接收别的玩家的CollaborationData后需调用此方法以更新session
            arView.session.update(with: collaborationData)
            return
        }
        
        let sessionIDCommandString = "SessionID:"
        // data解码后变成的字符串若以“SessionID:”为开头,则取出“SessionID:”后面的字串(详见playground)
        if let commandString = String(data: data, encoding: .utf8), commandString.starts(with: sessionIDCommandString) {
            let newSessionID = String(
                commandString[
                    commandString.index(commandString.startIndex,offsetBy: sessionIDCommandString.count)...
                ]
            )
            // 如果当前过来联机的玩家之前使用的是不同的sessionID,则删除所有相关anchor
            if let oldSessionID = peerSessionIDs[peer] {
                removeAllAnchorsOriginatingFromARSessionWithID(oldSessionID)
            }
            
            peerSessionIDs[peer] = newSessionID
        }
    }

至此项目整体逻辑就到此为止了,接下来补充一些未解释的坑。

1.玩家断开连接后删除该玩家创造的所有锚点

//4-连接断开(玩家离开)
    func peerLeft(_ peer: MCPeerID) {
        messageLabel.displayMessage("一个玩家离开了游戏")
        
        // 删除和刚刚离开的玩家相关的所有锚点
        if let sessionID = peerSessionIDs[peer] {
            removeAllAnchorsOriginatingFromARSessionWithID(sessionID)
            peerSessionIDs.removeValue(forKey: peer)
        }
    }

2.点击事件生成正方体依赖的锚点

  @objc func handleTap(recognizer: UITapGestureRecognizer) {
        let location = recognizer.location(in: arView)
        
        // 在用户触摸位置的水平面上找到位置数据
        // raycast为hit-testing的升级版
        let results = arView.raycast(from: location, allowing: .estimatedPlane, alignment: .horizontal)
        if let firstResult = results.first {
            // 在触摸位置添加一个带有name的ARAnchor,之后将在“session_:didAdd :)”中对应该name
            let anchor = ARAnchor(name: "Anchor for object placement", transform: firstResult.worldTransform)
            arView.session.add(anchor: anchor)
        } else {
            messageLabel.displayMessage("没有找到平面无法放置物体\n请寻找平坦表面", duration: 6.0)
        }
    }

其他的一些功能实现就在源代码里找就行,对照着看一看就能懂,指导用户操作的辅助功能也不在此讲解了。

总结一下

1.通过MultipeerConnectivity协议进行多用户的互联(三次握手)

2.启用短距离通讯协议在didOutputCollaborationData中实现数据的传递

3.在didAdd中判断是否连接成功,并多个设备同时更新和渲染画面

4.前三条实现正向通信和反向通信的具体操作均需要在viewcontroller中实现

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AR联机初探+官方项目代码解析 的相关文章

  • 核心图:如何隐藏图、轴和标签?

    我有一个图表 其中 Y 轴和 X 轴与主图一起绘制在默认绘图空间上 然后我有单独的辅助绘图空间 每个辅助绘图都有自己的 Y 轴 所有绘图的 X 轴都相同 我正在实现按钮来打开和关闭辅助图 我希望它基本上包括整个绘图空间 绘图 自定义 y 轴
  • XCode 12.5 缺少权利 com.apple.developer.linked-appclip-app-identifiers

    将 Xcode 版本更新到 12 5 后 我遇到了 App Clip 问题 在 App Store Connect 上传期间 我收到警告 TMS 90876 Missing entitlement This app contains an
  • Xcode 6 iOS 8 UIImage imageNamed 来自捆绑包问题

    我使用 iOS 7 1 构建我的项目 并尝试使用存储在 images cars car 1 png 中的图像加载 UIImage 视图 所有图像都位于项目树中的图像文件夹中 如下图所示 所以它非常适合 iOS 7 1 和 Xcode 5 但
  • 解雇ViewControllerAnimated:完成:在 iOS 8 上

    在 iOS dismissViewControllerAnimated completion 会导致presentedViewController being nil 在 iOS 8 中 presentedViewController仍然指
  • 从 iPhone 上的 NSString 中删除 HTML 标签

    有几种不同的方法可以删除HTML tags从一个NSString in Cocoa One way http cocoa karelia com Foundation Categories NSString Flatten a string
  • UICollectionView 项目顺序在从右到左语言中不颠倒

    我注意到一个大问题 在从右到左的语言中 单元格顺序没有正确颠倒 只有对齐是正确的 但仅适用于水平流布局 并且如果集合视图包含不同的细胞大小 是的 我知道这听起来很疯狂 如果所有单元格大小相同 则排序和对齐就很好 这是到目前为止我通过示例应用
  • 如何在文本末尾添加按钮,如 Facebook 的“继续阅读”?

    当状态帖子太长时 Facebook 应用程序会剪切文本并在末尾添加 继续阅读 它如何知道在哪里剪切文本并添加 继续阅读 不仅仅是向 textView 或标签添加按钮 而是如何剪切字符串 例如 在下图中 我将行数限制为 7 我可以在 text
  • 将用户重定向到 iTunes 应用商店或 Google Play 商店?

    我正在寻找一种简单的解决方案来发布我的应用程序的一个链接 例如在 Facebook 上 如果用户使用移动设备访问它 它应该自动重定向到正确的应用程序商店 否则 用户应该被重定向到我的网站 iOS应用程序 http itunes apple
  • 当 iPhone 设备方向朝上/朝下时,我可以判断它是横向还是纵向吗?

    我得到这个代码 如果设备处于左 右横向或上下颠倒状态 它会旋转并显示另一个视图控制器 但如果它的方向朝上或朝下 那么我如何判断它是横向模式还是纵向模式 因为我只想在它面朝上或朝下以及横向模式下旋转 void viewDidAppear BO
  • 将 HTML 字符串加载到 UIWebView 中的延迟

    我在导航控制器中有两个视图控制器 第一个视图控制器有一个带有按钮的菜单 按下此按钮将移动到第二个视图控制器并将 html 字符串加载到 UIWebView 中 没有其他东西被加载到 webview 中 只是一个简单的 NSString 其中
  • iOS 复合谓词

    我正在编写一个具有照片数据库的应用程序 每张照片都有多个与之关联的标签 并且该应用程序有一个带有大量切换的搜索页面 允许用户仅根据他们感兴趣的标签搜索照片 每个标签都存储了integerID 是因为它们对应于外部数据库的 ID 所以我尝试简
  • 使用导航控制器在 Storyboard 中呈现视图控制器 - Swift

    我目前在下面的新故事板中显示了一个 viewController var storyboard UIStoryboard UIStoryboard name AccountStoryboard bundle nil var vc Welco
  • iPhone / iPad IOS 应用程序仪器内存计数与 task_info 内存计数

    我一直在使用 Instruments Leak Tester 它给出了大约 1 3 meg 的应用程序总分配数字 但是 当使用 task info 时 它会报告更大的内存量 例如 10 20 meg 我想我只是想确认task info正在返
  • 连接到 Apple Music

    所以我尝试使用 React Native 应用程序从 iOS 设备连接到 Apple Music 有一个 API 可以执行相同的操作 但我需要从 storekit 框架调用一个函数 提出个性化请求 苹果音乐API https develop
  • SDWebImage 显示缓存中图像的占位符

    在 iOS 5 1 项目 iPad 中使用 SDWebImage 3 我们展示相当大的图像 700x500 并且我们有很多图像 1000 我们预取图像并缓存到磁盘 然后允许用户浏览它们 效果很好 除了当您浏览图像时 您总是会看到占位符显示一
  • 带约束的 Swift 动画

    是否可以通过改变约束来制作 UIView 动画 基本上 我想要动画myv UIView 具有 x y 高度和宽度约束 使用 UIView animateWithDuration 1 5 通过改变旧的限制 是的 这是可能的 你可以这样做 fu
  • 如何在 XCode5 中将部署目标更改为 5.1.1 [重复]

    这个问题在这里已经有答案了 我正在一个项目中工作 我需要支持 iOS 5 1 1 但在 部署目标 的下拉菜单中我没有 5 1 1 作为选项 我的问题是如何将 iOS 5 1 1 添加为部署目标 我将非常感谢你的帮助 如果您愿意 您可以在框中
  • NSURLConnection 是否自动保留从服务器发送的 cookie?

    我从 ios 登录到我的龙卷风后端并发回 secure cookie 我注意到只要验证我设置的 secure cookie 我还可以请求其他信息 NSURLConnection 会保留 cookie 多久 或者关闭应用程序后 cookie
  • Swift 3 和 Xcode8 - init 的使用不明确

    在我安装 Xcode 8 并将项目转换为 Swift 3 之前 以下行没问题 现在转换后看起来像这样 let valueData Data Data bytes UnsafePointer
  • 如何将 NSAppTransportSecurity 添加到 Cordova 项目

    我正在从事一个 ionic cordova 项目 该应用程序需要配置 iOS 9 版本的应用程序传输安全例外 有谁知道如何将以下配置添加到 cordova 项目配置文件中 配置 xml

随机推荐

  • SO、SOP、SOIC、MSOP、TSSOP、TSOP、VSSOP、SSOP、SOJ封装详解

    1 简要信息如下 2 SOP和SOIC的规格多是类似的 现在大多数厂商基本都采用的是SOIC的描述 SOIC8有窄体150mil的 外形封装宽度 不含管脚 下同 管脚间距是1 27mm 如下 有宽体的208mil的 管脚间距是1 27mm
  • [ali编程题练习] 小强去春游

    小强作为强班的班长 决定带着包含他在内的n个同学去春游 路程走到一半 发现前面有一条河流 且只有一条小船 经过实验后发现 这个小船一次最多只能运送两个人 而且过河的时间是等于两个人中体重较大的那个人的体重 如果只有一个人 那么过河时间就是这
  • 陈天奇等人提出TVM:深度学习自动优化代码生成器

    TVM 是由华盛顿大学在读博士陈天奇等人提出的深度学习自动代码生成方法 去年 8 月机器之心曾对其进行过简要介绍 该技术能自动为大多数计算硬件生成可部署优化代码 其性能可与当前最优的供应商提供的优化计算库相比 且可以适应新型专用加速器后端
  • less和css的区别是什么

    一 less与css介绍 Less 是一门 CSS 预处理语言 它扩展了 CSS 语言 增加了变量 Mixin 函数等特性 使 CSS 更易维护和扩展 LESS是一个CSS预处理器 可以为网站启用可自定义 可管理和可重用的样式表 LESS是
  • 力扣算法题

    1 合并两个有序数组 简单 给你两个有序整数数组 nums1 和 nums2 请你将 nums2 合并到 nums1 中 使 nums1 成为一个有序数组 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 你可以假设 num
  • 今天来聊聊笔记本电脑的使用禁忌

    随着科技的不断进步 笔记本电脑已经成为我们生活 学习和工作中不可或缺的一部分 然而 在享受便携性和高效性的同时 我们也需要注意一些使用禁忌 以保护我们的笔记本电脑 延长其使用寿命 本文将从四个方面为您介绍笔记本电脑使用的禁忌 第一部分 不透
  • UEFI启动U盘制作

    说明 在网上搜索了一下UEFI启动U盘工具 发现都是一些超级大的工具 动不动就是上百兆 而且之前使用老毛桃安装系统 发现会在系统中安装很多其他的软件 心有余悸 所以打算找一个干净的工具 随后在网上搜索找到rufus 使用 下载rufus h
  • QObject::connect()函数使用的几个注意点

    一 connect 函数有且只能在QObject类里面和QObject派生类里面使用 connect 函数有且只能在QObject类里面和QObject派生类里面使用 自己新建的类里面 基类不是QObject类和其QObject派生类 使用
  • html 怎么做到大小屏兼容,CSS兼容各个屏幕大小的写法

    media screen and max width 1100px 此处写最大屏幕是1100px的css media screen and max width 978px 此处写最大屏幕是978px的css media screen and
  • 一文读懂Embedding

    文章目录 一 什么是Embedding 二 One Hot编码 三 怎么理解Embedding 四 Word Embedding 一 什么是Embedding Embedding 直译是嵌入式 嵌入层 简单来说 我们常见的地图就是对于现实地
  • win10下用tensorflow object detection API训练自己的数据出错

    gtf36 D models master models research object detection gt python model main py pipeline config path mytraining faster rc
  • 人脸识别项目介绍

    项目介绍 人脸识别 从Python或命令行中识别和操作面部 世界上最简单的人脸识别库 使用dlib的最新人脸识别功能构建 建立在深度学习之上 该模型的精度为99 38 Wild 基准中的标记面孔 这也提供了一个简单的face recogni
  • 论文学习笔记:Generalized Intersection over Union: A Metric and A Loss for Bounding Box Regressi

    Generalized Intersection over Union A Metric and A Loss for Bounding Box Regression 作者 Hamid Rezatofighi Nathan Tsoi Jun
  • STM32/STM8+DMX512协议

    标准DMX512协议 https blog csdn net qq 42992084 article details 98525578 这位大佬有详细介绍 因为标准DMX512协议在现有的MCU上很难达到或者说很浪费资源 所以大多采用非标准
  • Java基础知识——lamda表达式和函数式接口

    文章目录 一 Lambda表达式 二 函数式接口 2 1 函数式接口作用 2 2 常用函数式接口 Supplier接口 2 3 常用函数式接口 Consumer接口 2 4 常用函数式接口 Predicate接口 2 5 常用函数式接口 F
  • 使用带域名的docker容器

    之前要研究一些新兴技术时 都是在自己电脑上安装虚拟机 使用起来有些许不变 例如想搭建一套mysql的双主半同步 并使用keepalived来保证高可用 就需要创建2台虚拟机 很是不方便 于是就寻找使用更方便的虚拟技术 docker就可以很好
  • java手工分页工具类

    手工分页 手工分页用处 当数据使用mybatisPlus分页查询后 需要再次过滤掉筛选数据时 返回的总数失效 此时可以使用手工分页 kotlin手工分页工具类 Component class ManualPageUtils 封装手动分页 p
  • java定时器实现原理_java timer(定时器)执行原理分析

    1 首先看Timer源码 public class Timer private final TaskQueue queue new TaskQueue private final TimerThread thread new TimerTh
  • 文件编译【上】

    1 程序的翻译环境和执行环境 翻译环境 在翻译环境中 源文件被翻译为可执行的机器指令 执行环境 实际执行代码的环境 2 编译与链接 组成一个程序的每个源文件通过编译过程分别转换成目标代码 object code 每个目标文件由链接器 lin
  • AR联机初探+官方项目代码解析

    学AR也有一小段时间了 今天给大家分享一下如何让两部以上的设备查看到相同的增强现实景象 在这里作者就以苹果官方的示例来进行解析 一定要把代码下载了和文章对照着看 不然会懵 官方项目代码地址 https developer apple com