XR和Steam VR项目合并问题

最近有一个项目是用Steam VR开发的,里面部分场景是用VRTK框架做的,还有一部分是用SteamVR SDK自带的Player预制直接开发的。

这样本身没有问题,因为最终都是通过SteamVR SDK处理的,VRTK也管理好了SteamVR的逻辑,并且支持动态切换,比如切换成Oculus的。

然后现在遇到一个问题,还有一个项目是用Unity自带的XR开发的,Package Manager导入XR相关的插件实现的。

 

需要将XR开发的项目移植到Steam VR项目来,然后事情就开始了。

SteamVR的场景可以运行,通过Pico以及Quest串流还有htc头盔都能正常识别,手柄也能控制。

但是XR场景就出现问题了,头盔无法识别。

经过一步步排查,发现是XR Plug-in Management这里需要设置不同的XRLoader。

而SteamVR是OpenVR Loader,而XR是OpenXR,因为OpenVR Loader在前,所以激活的是OpenVR Loader,这也是为什么SteamVR场景可以运行而XR场景不行。

我们看看unity的源代码是怎么写的,发现这里面是有activeLoader的概念,也就是一次只能一个Loader运行。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;using UnityEditor;using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UIElements;
using UnityEngine.Serialization;
using UnityEngine.XR.Management;[assembly: InternalsVisibleTo("Unity.XR.Management.Tests")]
[assembly: InternalsVisibleTo("Unity.XR.Management.EditorTests")]
namespace UnityEngine.XR.Management
{/// <summary>/// Class to handle active loader and subsystem management for XR. This class is to be added as a/// ScriptableObject asset in your project and should only be referenced by the <see cref="XRGeneralSettings"/>/// instance for its use.////// Given a list of loaders, it will attempt to load each loader in the given order. The first/// loader that is successful wins and all remaining loaders are ignored. The loader/// that succeeds is accessible through the <see cref="activeLoader"/> property on the manager.////// Depending on configuration the <see cref="XRManagerSettings"/> instance will automatically manage the active loader/// at correct points in the application lifecycle. The user can override certain points in the active loader lifecycle/// and manually manage them by toggling the <see cref="automaticLoading"/> and <see cref="automaticRunning"/>/// properties. Disabling <see cref="automaticLoading"/> implies the the user is responsible for the full lifecycle/// of the XR session normally handled by the <see cref="XRManagerSettings"/> instance. Toggling this to false also toggles/// <see cref="automaticRunning"/> false.////// Disabling <see cref="automaticRunning"/> only implies that the user is responsible for starting and stopping/// the <see cref="activeLoader"/> through the <see cref="StartSubsystems"/> and <see cref="StopSubsystems"/> APIs.////// Automatic lifecycle management is executed as follows////// * Runtime Initialize -> <see cref="InitializeLoader"/>. The loader list will be iterated over and the first successful loader will be set as the active loader./// * Start -> <see cref="StartSubsystems"/>. Ask the active loader to start all subsystems./// * OnDisable -> <see cref="StopSubsystems"/>. Ask the active loader to stop all subsystems./// * OnDestroy -> <see cref="DeinitializeLoader"/>. Deinitialize and remove the active loader./// </summary>public sealed class XRManagerSettings : ScriptableObject{[HideInInspector]bool m_InitializationComplete = false;#pragma warning disable 414// This property is only used by the scriptable object editing part of the system and as such no one// directly references it. Have to manually disable the console warning here so that we can// get a clean console report.[HideInInspector][SerializeField]bool m_RequiresSettingsUpdate = false;
#pragma warning restore 414[SerializeField][Tooltip("Determines if the XR Manager instance is responsible for creating and destroying the appropriate loader instance.")][FormerlySerializedAs("AutomaticLoading")]bool m_AutomaticLoading = false;/// <summary>/// Get and set Automatic Loading state for this manager. When this is true, the manager will automatically call/// <see cref="InitializeLoader"/> and <see cref="DeinitializeLoader"/> for you. When false <see cref="automaticRunning"/>/// is also set to false and remains that way. This means that disabling automatic loading disables all automatic behavior/// for the manager./// </summary>public bool automaticLoading{get { return m_AutomaticLoading; }set { m_AutomaticLoading = value; }}[SerializeField][Tooltip("Determines if the XR Manager instance is responsible for starting and stopping subsystems for the active loader instance.")][FormerlySerializedAs("AutomaticRunning")]bool m_AutomaticRunning = false;/// <summary>/// Get and set automatic running state for this manager. When set to true the manager will call <see cref="StartSubsystems"/>/// and <see cref="StopSubsystems"/> APIs at appropriate times. When set to false, or when <see cref="automaticLoading"/> is false/// then it is up to the user of the manager to handle that same functionality./// </summary>public bool automaticRunning{get { return m_AutomaticRunning; }set { m_AutomaticRunning = value; }}[SerializeField][Tooltip("List of XR Loader instances arranged in desired load order.")][FormerlySerializedAs("Loaders")]List<XRLoader> m_Loaders = new List<XRLoader>();// Maintains a list of registered loaders that is immutable at runtime.[SerializeField][HideInInspector]HashSet<XRLoader> m_RegisteredLoaders = new HashSet<XRLoader>();/// <summary>/// List of loaders currently managed by this XR Manager instance./// </summary>/// <remarks>/// Modifying the list of loaders at runtime is undefined behavior and could result in a crash or memory leak./// Use <see cref="activeLoaders"/> to retrieve the currently ordered list of loaders. If you need to mutate/// the list at runtime, use <see cref="TryAddLoader"/>, <see cref="TryRemoveLoader"/>, and/// <see cref="TrySetLoaders"/>./// </remarks>[Obsolete("'XRManagerSettings.loaders' property is obsolete. Use 'XRManagerSettings.activeLoaders' instead to get a list of the current loaders.")]public List<XRLoader> loaders{get { return m_Loaders; }
#if UNITY_EDITORset { m_Loaders = value; }
#endif}/// <summary>/// A shallow copy of the list of loaders currently managed by this XR Manager instance./// </summary>/// <remarks>/// This property returns a read only list. Any changes made to the list itself will not affect the list/// used by this XR Manager instance. To mutate the list of loaders currently managed by this instance,/// use <see cref="TryAddLoader"/>, <see cref="TryRemoveLoader"/>, and/or <see cref="TrySetLoaders"/>./// </remarks>public IReadOnlyList<XRLoader> activeLoaders => m_Loaders;/// <summary>/// Read only boolean letting us know if initialization is completed. Because initialization is/// handled as a Coroutine, people taking advantage of the auto-lifecycle management of XRManager/// will need to wait for init to complete before checking for an ActiveLoader and calling StartSubsystems./// </summary>public bool isInitializationComplete{get { return m_InitializationComplete; }}///<summary>/// Return the current singleton active loader instance.///</summary>[HideInInspector]public XRLoader activeLoader { get; private set; }/// <summary>/// Return the current active loader, cast to the requested type. Useful shortcut when you need/// to get the active loader as something less generic than XRLoader./// </summary>////// <typeparam name="T">Requested type of the loader</typeparam>////// <returns>The active loader as requested type, or null.</returns>public T ActiveLoaderAs<T>() where T : XRLoader{return activeLoader as T;}/// <summary>/// Iterate over the configured list of loaders and attempt to initialize each one. The first one/// that succeeds is set as the active loader and initialization immediately terminates.////// When complete <see cref="isInitializationComplete"/> will be set to true. This will mark that it is safe to/// call other parts of the API. This does not guarantee that init successfully created a loader. For that/// you need to check that ActiveLoader is not null.////// Note that there can only be one active loader. Any attempt to initialize a new active loader with one/// already set will cause a warning to be logged and immediate exit of this function.////// This method is synchronous and on return all state should be immediately checkable.////// <b>If manual initialization of XR is being done, this method can not be called before Start completes/// as it depends on graphics initialization within Unity completing.</b>/// </summary>public void InitializeLoaderSync(){if (activeLoader != null){Debug.LogWarning("XR Management has already initialized an active loader in this scene." +" Please make sure to stop all subsystems and deinitialize the active loader before initializing a new one.");return;}foreach (var loader in currentLoaders){if (loader != null){if (CheckGraphicsAPICompatibility(loader) && loader.Initialize()){activeLoader = loader;m_InitializationComplete = true;return;}}}activeLoader = null;}/// <summary>/// Iterate over the configured list of loaders and attempt to initialize each one. The first one/// that succeeds is set as the active loader and initialization immediately terminates.////// When complete <see cref="isInitializationComplete"/> will be set to true. This will mark that it is safe to/// call other parts of the API. This does not guarantee that init successfully created a loader. For that/// you need to check that ActiveLoader is not null.////// Note that there can only be one active loader. Any attempt to initialize a new active loader with one/// already set will cause a warning to be logged and immediate exit of this function.////// Iteration is done asynchronously and this method must be called within the context of a Coroutine.////// <b>If manual initialization of XR is being done, this method can not be called before Start completes/// as it depends on graphics initialization within Unity completing.</b>/// </summary>////// <returns>Enumerator marking the next spot to continue execution at.</returns>public IEnumerator InitializeLoader(){if (activeLoader != null){Debug.LogWarning("XR Management has already initialized an active loader in this scene." +" Please make sure to stop all subsystems and deinitialize the active loader before initializing a new one.");yield break;}foreach (var loader in currentLoaders){if (loader != null){if (CheckGraphicsAPICompatibility(loader) && loader.Initialize()){activeLoader = loader;m_InitializationComplete = true;yield break;}}yield return null;}activeLoader = null;}/// <summary>/// Attempts to append the given loader to the list of loaders at the given index./// </summary>/// <param name="loader">/// The <see cref="XRLoader"/> to be added to this manager's instance of loaders./// </param>/// <param name="index">/// The index at which the given <see cref="XRLoader"/> should be added. If you set a negative or otherwise/// invalid index, the loader will be appended to the end of the list./// </param>/// <returns>/// <c>true</c> if the loader is not a duplicate and was added to the list successfully, <c>false</c>/// otherwise./// </returns>/// <remarks>/// This method behaves differently in the Editor and during runtime/Play mode. While your app runs in the Editor and not in/// Play mode, attempting to add an <see cref="XRLoader"/> will always succeed and register that loader's type/// internally. Attempting to add a loader during runtime/Play mode will trigger a check to see whether a loader of/// that type was registered. If the check is successful, the loader is added. If not, the loader is not added and the method/// returns <c>false</c>./// </remarks>public bool TryAddLoader(XRLoader loader, int index = -1){if (loader == null || currentLoaders.Contains(loader))return false;#if UNITY_EDITORif (!EditorApplication.isPlaying && !m_RegisteredLoaders.Contains(loader))m_RegisteredLoaders.Add(loader);
#endifif (!m_RegisteredLoaders.Contains(loader))return false;if (index < 0 || index >= currentLoaders.Count)currentLoaders.Add(loader);elsecurrentLoaders.Insert(index, loader);return true;}/// <summary>/// Attempts to remove the first instance of a given loader from the list of loaders./// </summary>/// <param name="loader">/// The <see cref="XRLoader"/> to be removed from this manager's instance of loaders./// </param>/// <returns>/// <c>true</c> if the loader was successfully removed from the list, <c>false</c> otherwise./// </returns>/// <remarks>/// This method behaves differently in the Editor and during runtime/Play mode. During runtime/Play mode, the loader/// will be removed with no additional side effects if it is in the list managed by this instance. While in the/// Editor and not in Play mode, the loader will be removed if it exists and/// it will be unregistered from this instance and any attempts to add it during/// runtime/Play mode will fail. You can re-add the loader in the Editor while not in Play mode./// </remarks>public bool TryRemoveLoader(XRLoader loader){var removedLoader = true;if (currentLoaders.Contains(loader))removedLoader = currentLoaders.Remove(loader);#if UNITY_EDITORif (!EditorApplication.isPlaying && !currentLoaders.Contains(loader))m_RegisteredLoaders.Remove(loader);
#endifreturn removedLoader;}/// <summary>/// Attempts to set the given loader list as the list of loaders managed by this instance./// </summary>/// <param name="reorderedLoaders">/// The list of <see cref="XRLoader"/>s to be managed by this manager instance./// </param>/// <returns>/// <c>true</c> if the loader list was set successfully, <c>false</c> otherwise./// </returns>/// <remarks>/// This method behaves differently in the Editor and during runtime/Play mode. While in the Editor and not in/// Play mode, any attempts to set the list of loaders will succeed without any additional checks. During/// runtime/Play mode, the new loader list will be validated against the registered <see cref="XRLoader"/> types./// If any loaders exist in the list that were not registered at startup, the attempt will fail./// </remarks>public bool TrySetLoaders(List<XRLoader> reorderedLoaders){var originalLoaders = new List<XRLoader>(activeLoaders);
#if UNITY_EDITORif (!EditorApplication.isPlaying){registeredLoaders.Clear();currentLoaders.Clear();foreach (var loader in reorderedLoaders){if (!TryAddLoader(loader)){TrySetLoaders(originalLoaders);return false;}}return true;}
#endifcurrentLoaders.Clear();foreach (var loader in reorderedLoaders){if (!TryAddLoader(loader)){currentLoaders = originalLoaders;return false;}}return true;}private bool CheckGraphicsAPICompatibility(XRLoader loader){GraphicsDeviceType deviceType = SystemInfo.graphicsDeviceType;List<GraphicsDeviceType> supportedDeviceTypes = loader.GetSupportedGraphicsDeviceTypes(false);// To help with backward compatibility, if the compatibility list is empty we assume that it does not implement the GetSupportedGraphicsDeviceTypes method// Therefore we revert to the previous behavior of building or starting the loader regardless of gfx api settings.if (supportedDeviceTypes.Count > 0 && !supportedDeviceTypes.Contains(deviceType)){Debug.LogWarning(String.Format("The {0} does not support the initialized graphics device, {1}. Please change the preffered Graphics API in PlayerSettings. Attempting to start the next XR loader.", loader.name, deviceType.ToString()));return false;}return true;}/// <summary>/// If there is an active loader, this will request the loader to start all the subsystems that it/// is managing.////// You must wait for <see cref="isInitializationComplete"/> to be set to true prior to calling this API./// </summary>public void StartSubsystems(){if (!m_InitializationComplete){Debug.LogWarning("Call to StartSubsystems without an initialized manager." +"Please make sure wait for initialization to complete before calling this API.");return;}if (activeLoader != null){activeLoader.Start();}}/// <summary>/// If there is an active loader, this will request the loader to stop all the subsystems that it/// is managing.////// You must wait for <see cref="isInitializationComplete"/> to be set to tru prior to calling this API./// </summary>public void StopSubsystems(){if (!m_InitializationComplete){Debug.LogWarning("Call to StopSubsystems without an initialized manager." +"Please make sure wait for initialization to complete before calling this API.");return;}if (activeLoader != null){activeLoader.Stop();}}/// <summary>/// If there is an active loader, this function will deinitialize it and remove the active loader instance from/// management. We will automatically call <see cref="StopSubsystems"/> prior to deinitialization to make sure/// that things are cleaned up appropriately.////// You must wait for <see cref="isInitializationComplete"/> to be set to tru prior to calling this API.////// Upon return <see cref="isInitializationComplete"/> will be rest to false;/// </summary>public void DeinitializeLoader(){if (!m_InitializationComplete){Debug.LogWarning("Call to DeinitializeLoader without an initialized manager." +"Please make sure wait for initialization to complete before calling this API.");return;}StopSubsystems();if (activeLoader != null){activeLoader.Deinitialize();activeLoader = null;}m_InitializationComplete = false;}// Use this for initializationvoid Start(){if (automaticLoading && automaticRunning){StartSubsystems();}}void OnDisable(){if (automaticLoading && automaticRunning){StopSubsystems();}}void OnDestroy(){if (automaticLoading){DeinitializeLoader();}}// To modify the list of loaders internally use `currentLoaders` as it will return a list reference rather// than a shallow copy.// TODO @davidmo 10/12/2020: remove this in next major version bump and make 'loaders' internal.internal List<XRLoader> currentLoaders{get { return m_Loaders; }set { m_Loaders = value; }}// To modify the set of registered loaders use `registeredLoaders` as it will return a reference to the// hashset of loaders.internal HashSet<XRLoader> registeredLoaders{get { return m_RegisteredLoaders; }}}
}

事情变的有趣起来,我们知道了这样的原理之后,那鱼蛋我就想着尝试下,在Runtime里动态切换行吧,SteamVR场景切换到OpenVR Loader,而XR场景切换到OpenXR,代码如下。

using System.Collections.Generic;
using Unity.XR.OpenVR;
using UnityEngine;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR;namespace EgoGame
{/// <summary>/// 该类有问题,废弃了/// </summary>public class AutoXRLoader:MonoBehaviour{public List<XRLoader> xrLoaders;public List<XRLoader> vrLoaders;public bool isXR;private void Awake(){SetLoader(isXR);}private void OnDestroy(){SetLoader(!isXR);}void SetLoader(bool xr){//不这样,会频繁的退出loader,VR会没画面if (xr && XRGeneralSettings.Instance.Manager.activeLoader is OpenXRLoader){return;}if (!xr && XRGeneralSettings.Instance.Manager.activeLoader is OpenVRLoader){return;}var loaders = xr ? xrLoaders : vrLoaders;Debug.Log("切换Loader:" + xr+"=="+XRGeneralSettings.Instance.Manager.activeLoader);XRGeneralSettings.Instance.Manager.DeinitializeLoader();XRGeneralSettings.Instance.Manager.TrySetLoaders(loaders);XRGeneralSettings.Instance.Manager.InitializeLoaderSync();XRGeneralSettings.Instance.Manager.StartSubsystems();}}
}

果然奏效了,XR场景能在头盔里识别并运行了,手柄也能控制。但是,切到SteamVR场景就出现了问题,Steam VR SDK报错了,报错提示有另一个应用在使用SteamVR。

最后的结果就是,没法实现动态切换XR或VR,如果看到此处的人,有办法请告诉我,我尝试了两天用了各种办法,都没法做到。

最后推荐大家开发VR应用不要直接用SteamVR SDK或XR SDK或Oculus SDK开发,而是用那些集成的插件,如VR Interaction Framework、VRTK等,这样在多个VR设备也能快速部署。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/24638.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构之初识泛型

目录&#xff1a; 一.什么是泛型 二.引出泛型 三.泛型语法及&#xff0c;泛型类的使用和裸类型(Raw Type) 的了解 . 四.泛型的编译&#xff1a; 五.泛型的上界 六.泛型方法 注意&#xff1a;在看泛型之前可以&#xff0c;回顾一下&#xff0c;包装类&#xff0c;包装类就是服务…

JSP中连接数据库MySQL

JSP中连接数据库MySQL 一、软件环境 下载并安装MySQL&#xff0c;Tomacat&#xff0c;JDBC、IDEA或其他IDE&#xff0c;本文使用IDEA 二、环境配置 将其环境变量配置好之后&#xff0c;下载Java 专用的连接MySQL的驱动包JDBC。 下载链接&#xff1a;https://dev.mysql.com/…

AI高考大战,揭秘五大热门模型谁能问鼎数学之巅?

在高考前&#xff0c;我就有想法了&#xff0c;这一次让AI来做做高考题。就用国内的大模型&#xff0c;看哪家的大模型解题最厉害。 第一天考完&#xff0c;就拿到了2024高考数学2卷的电子版&#xff0c;这也是重庆市采用的高考试卷 这次选了5个AI工具&#xff0c;分别是天工&a…

帕友饮食改善的小建议!

一、增加膳食纤维的摄入 帕金森病患者应增加膳食纤维的摄入量&#xff0c;以帮助调节肠道功能&#xff0c;预防便秘。膳食纤维丰富的食物包括蔬菜、水果、全谷类食物等。患者应确保每天摄入足够的膳食纤维&#xff0c;以保持肠道通畅&#xff0c;缓解帕金森病可能带来的消化不…

[AIGC] Springboot 自动配置的作用及理由

在详细解释SpringBoot的自动配置之前&#xff0c;先介绍以下背景知识。在创建现代复杂的应用程序时&#xff0c;一个困难的部分是正确地设置您的开发环境。这个问题尤其在Java世界中尤为突出&#xff0c;因为您必须管理和配置许多独立的标准和技术。 当我们谈论Spring Boot的自…

【Unity】官方文档学习-光照系统

目录 1 前言 2 光照介绍 2.1 直接光与间接光 2.2 实时光照与烘焙光照 2.3 全局光照 3 光源 3.1 Directional Light 3.1.1 Color 3.1.2 Mode 3.1.3 Intensity 3.1.4 Indirect Multiplier 3.1.5 Shadow Type 3.1.6 Baked Shadow Angle 3.1.7 Realtime Shadows 3.1…

数据挖掘--分类

数据挖掘--引论 数据挖掘--认识数据 数据挖掘--数据预处理 数据挖掘--数据仓库与联机分析处理 数据挖掘--挖掘频繁模式、关联和相关性&#xff1a;基本概念和方法 数据挖掘--分类 数据挖掘--聚类分析&#xff1a;基本概念和方法 基本概念 决策树归纳 决策树:决策树是一…

如何提高网站访问量?

提高网站访问量通常需要一个多方面的策略&#xff0c;涉及SEO、内容营销、社交媒体和其他网络营销手段&#xff0c;而我们仅从seo入手来说说 关键词优化是SEO的核心&#xff0c;它确保网站能够针对潜在用户的搜索查询进行优化。这不仅涉及在网站内容中使用正确的关键词 还需要…

问题:前肢的前方称() #微信#经验分享#微信

问题&#xff1a;前肢的前方称&#xff08;&#xff09; A . 掌侧 B . 跖侧 C . 背侧 D . 胫侧 E . 桡侧 参考答案如图所示

TOGAF数字化转型的关键(文尾附在线TOGAF免费测试)

业务架构驱动数据架构和应用架构的设计&#xff0c;而应用架构又依赖于数据架构和技术架构的支持。技术架构则为整个架构提供了稳定的基础设施。 在数字化转型中&#xff0c;协调和整合这四种架构是至关重要的。通过确保它们之间的一致性和协同工作&#xff0c;可以实现企业业务…

极简主义在UI设计中的应用及解析

极简主义&#xff0c;即“少就是多”。在设计中&#xff0c;极简主义是许多艺术概念之一&#xff0c;它描述了一种内容形式&#xff0c;可以在许多方面使用。现在移动UI界面和网页设计中的极简主义设计越来越多。即时设计认为&#xff0c;极简主义UI界面不仅美观&#xff0c;而…

[经验] 昆山教育网(昆山教育网中小学报名) #媒体#职场发展#微信

昆山教育网&#xff08;昆山教育网中小学报名&#xff09; 昆山教育局网站 网站&#xff1a;昆山市教育局 昆山市教育局全面贯彻执行党和国家的教育方针、政策&#xff0c;落实有关教育工作的法律、法规&#xff1b;负责制定本市教育工作的实施意见和措施&#xff0c;并监督…

TriForce: 突破长序列生成瓶颈的分层投机解码技术

在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的长序列生成能力一直是研究的热点。然而&#xff0c;随着模型规模的增长&#xff0c;推理过程中的内存和计算瓶颈成为了限制其应用的主要障碍。为了解决这一问题&#xff0c;Carnegie Mellon University和…

1867java银证转账系统系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java银证转账系统系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助采用了java设计&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用web模式&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&a…

go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证

什么是JWT JWT,全名为JSON Web Token&#xff0c;是当下主流的一种服务端通信认证方式&#xff0c;具有轻量,无状态的特点&#xff0c;它实现了让我们在用户与服务器之间传递安全可靠的Json文本信息&#xff0c;它的使用过程主要是这样的&#xff1a; 当用户注册的时候&#x…

【百万字详解Redis】集群

文章目录 一、集群模式概述1.1、什么是集群模式1.2、集群模式特点1.3、集群工作方式 二、集群模式的搭建2.1、搭建前的准备2.2、修改集群配置2.3、启动redis服务2.4、创建集群2.5、查看redis服务状态2.6、进入一个节点2.7、测试操作 三、集群操作3.1、主从切换3.2、从节点操作3…

【Python】解决Python报错:ValueError: not enough values to unpack (expected 2, got 1)

​​​​ 文章目录 引言1. 错误详解2. 常见的出错场景2.1 函数返回值解包2.2 遍历含有不同长度元组的列表 3. 解决方案3.1 检查和调整返回值3.2 安全的解包操作 4. 预防措施4.1 使用异常处理4.2 单元测试 结语 引言 在Python编程中&#xff0c;ValueError 是一个常见的异常类…

2024年如何通过完善的工程化,从0到1手把手建立个人 react 组件库

本文聚焦于快速创建并部署个人的组件库&#xff0c;方便平时开发中将通用的组件抽出&#xff0c;也可用于简历上展示个人的组件成果~ 组件库体验地址&#xff1a;components-library 关于以上内容&#xff0c;你是否好奇如何实现的&#xff0c;对于大多数项目&#xff0c;诸如…

【C语言】预处理详解(上卷)

前言 预处理也是C语言中非常重要的存在。那么就详细地来了解一下吧。 预定义符号 C语言设置了一些预定义符号&#xff0c;可以直接使用&#xff0c;预定义符号也是在预处理期间处理的。 1 __FILE__ //进行编译的源文件 2 __LINE__ //文件当前的…

JavaSE——抽象类和接口

目录 一 .抽象类 1.抽象类概念 2.抽象类语法 3.抽象类特性 4.抽象类的作用 二. 接口 1.接口的概念 2.语法规则 3.接口的使用 4.接口特性 5.实现多个接口 6.接口间的继承 三.抽象类和接口的区别 一 .抽象类 1.抽象类概念 在面向对象的概念中&#xff0c;所有的对…