【UE4 C++】编程子系统 Subsystem
概述
定义
- Subsystems 是一套可以定义、自动实例化和释放的类的框架。可以将其理解为 GamePlay 级别的 Component
- 不支持网络赋值
- 4.22开始引入,4.24完善。(可以移植源码到更早之前的版本使用。源码在手,为所欲为)
五类 Subsystems 及其生命周期
UEngineSubsystem(继承自 UDynamicSubsystem,UDynamicSubsystem继承自 USubsystem)
UEngine* GEngine
代表引擎,数量1。 Editor或Runtime模式都是全局唯一,从进程启动开始创建,进程退出时销毁。UEngine::Init()
UEditorSubsystem(继承自 UDynamicSubsystem,UDynamicSubsystem继承自 USubsystem)
UEditorEngine* GEditor
代表编辑器,数量1。 顾名思义,只在编辑器下存在且全局唯一,从编辑器启动开始创建,到编辑器退出时销毁。UGameInstanceSubsystem (继承自 USubsystem)
UGameInstance* GameInstance
代表一场游戏,数量1。 从游戏的启动开始创建,游戏退出时销毁。这里的一场游戏指的是Runtime或PIE模式的运行的都算,一场游戏里可能会创建多个World切换。UWorldSubsystem (继承自 USubsystem)
UWorld* World
代表一个世界,数量可能>1。其生命周期,跟GameMode是一起的。(EWorldType:Game,Editor,PIE,EditorPreview,GamePreview等 )ULocalPlayerSubsystem (继承自 USubsystem)
ULocalPlayer* LocalPlayer:代表本地玩家,数量可能>1。
UE支持本地分屏多玩家类型的游戏,但往往最常见的是就只有一个。LocalPlayer虽然往往跟PlayerController一起访问,但是其生命周期其实是跟UGameInstance一起的(默认一开始的时候就创建好一定数量的本地玩家),或者更准确的说是跟LocalPlayer的具体数量挂钩(当然你也可以运行时动态调用AddLocalPlayer)。
为什么要使用 Subsystems
- 更适用的生命周期
- 引擎只支持一个 GameInstance ,运行周期是整个引擎的生命周期
- 自定义 ManagerActor,生命周期一般为当前 level 的生命周期
- Subsystems 的生命周期可以依存于Engine,Editor,World,LocalPlayer
- 更简
- GameInstance 或者自定义 ManagerActor,需要手动维控制创建、释放
- Subsystems 自动创建、释放,提供 Initialize()、Deinitialize(),并且可重载
- 更模块化、更优雅、更封装、更易于维护、移植复用
- GameInstance 中将任务系统,计分系统,经济系统、对话系统等多个Manager 写在一起,会变得臃肿
- 模块间的数据访问封装不够良好,容易污染
- 不利于业务逻辑模块的复用,特别是需要进行移植的时候,以及多个插件同时都有自己的 GameInstance
- Subsystems 可以为不同的 Manager 创建对应的Subsystems
- 如 Manager 划分,
- 任务系统Subsystem : UGameInstanceSubsystem
- 计分系统Subsystem : UGameInstanceSubsystem
- 经济系统Subsystem : UGameInstanceSubsystem
- 对话系统Subsystem : UGameInstanceSubsystem
- 更模块化,代码显得优雅
- 解耦性高,易于维护、分工协作;易于移植复用
- 模块间的数据访问具有更好的封装性
- 如 Manager 划分,
- GameInstance 中将任务系统,计分系统,经济系统、对话系统等多个Manager 写在一起,会变得臃肿
- 更友好的访问接口
- Subsystem 更像以全局变量的形式来访问
- 提供了 Python 脚本的访问,用于编写编辑器脚本或编写测试代码
- Subsystem 无需覆盖引擎类。
Subsystems 的使用
创建过程,涉及 FSubsystemCollectionBase
FSubsystemCollectionBase::Initialize()
FSubsystemCollectionBase::AddAndInitializeSubsystem()
FSubsystemCollectionBase::Deinitialize()
参考附录源码 或者
EngineDir\Engine\Source\Runtime\Engine\Private\Subsystems\SubsystemCollection.cpp
默认重载函数
- ShouldCreateSubsystem
- Initialize()
- Deinitialize()
C++ 访问
// UMyEngineSubsystem 获取
UMyEngineSubsystem* MyEngineSubsystem = GEngine->GetEngineSubsystem<UMyEngineSubsystem>(); // UMyEditorSubsystem 获取
UMyEditorSubsystem* MyEditorSubsystem = GEditor->GetEditorSubsystem<UMyEditorSubsystem>(); // UMyGameInstanceSubsystem 获取
//UGameInstance* GameInstance = GetWorld()->GetGameInstance();
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance();
UMyGameInstanceSubsystem* MyGameInstanceSubsystem = GameInstance->GetSubsystem<UMyGameInstanceSubsystem>(); // UMyWorldSubsystem 获取
UMyWorldSubsystem* MyWorldSubsystem = GetWorld()->GetSubsystem<UMyWorldSubsystem>(); // UMyLocalPlayerSubsystem 获取
ULocalPlayer* LocalPlayer = UGameplayStatics::GetPlayerController()->GetLocalPlayer();
UMyLocalPlayerSubsystem* MyLocalPlayerSubsystem = LocalPlayer->GetSubsystem<UMyLocalPlayerSubsystem>();
- 引擎自带 USubsystemBlueprintLibrary 访问方法
UCLASS()
class ENGINE_API USubsystemBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY() public: /** Get a Game Instance Subsystem from the Game Instance associated with the provided context */
UFUNCTION(BlueprintPure, Category = "Engine Subsystems", meta = (BlueprintInternalUseOnly = "true"))
static UEngineSubsystem* GetEngineSubsystem(TSubclassOf<UEngineSubsystem> Class); /** Get a Game Instance Subsystem from the Game Instance associated with the provided context */
UFUNCTION(BlueprintPure, Category = "GameInstance Subsystems", meta = (WorldContext = "ContextObject", BlueprintInternalUseOnly = "true"))
static UGameInstanceSubsystem* GetGameInstanceSubsystem(UObject* ContextObject, TSubclassOf<UGameInstanceSubsystem> Class); /** Get a Local Player Subsystem from the Local Player associated with the provided context */
UFUNCTION(BlueprintPure, Category = "LocalPlayer Subsystems", meta = (WorldContext = "ContextObject", BlueprintInternalUseOnly = "true"))
static ULocalPlayerSubsystem* GetLocalPlayerSubsystem(UObject* ContextObject, TSubclassOf<ULocalPlayerSubsystem> Class); /** Get a World Subsystem from the World associated with the provided context */
UFUNCTION(BlueprintPure, Category = "GameInstance Subsystems", meta = (WorldContext = "ContextObject", BlueprintInternalUseOnly = "true"))
static UWorldSubsystem* GetWorldSubsystem(UObject* ContextObject, TSubclassOf<UWorldSubsystem> Class); /**
* Get a Local Player Subsystem from the LocalPlayer associated with the provided context
* If the player controller isn't associated to a LocalPlayer nullptr is returned
*/
UFUNCTION(BlueprintPure, Category = "LocalPlayer Subsystems", meta = (BlueprintInternalUseOnly = "true"))
static ULocalPlayerSubsystem* GetLocalPlayerSubSystemFromPlayerController(APlayerController* PlayerController, TSubclassOf<ULocalPlayerSubsystem> Class); private:
static UWorld* GetWorldFrom(UObject* ContextObject);
};
UGameInstanceSubsystem
用法一
支持委托
支持普通变量和函数
支持蓝图调用
UCLASS()
class DESIGNPATTERNS_API UScoreGameInsSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// 是否允许被创建
virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; }
// 初始化
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// 释放
virtual void Deinitialize() override; // 声明委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FScoreChanged, int32, CurrentScore);
UPROPERTY(BlueprintAssignable)
FScoreChanged ScoreChange; UFUNCTION(BlueprintCallable, Category = "MySubsystem | ScoreGameInsSubsystem")
int32 AddScore(int32 BaseScore);
private:
int32 Score;
};
void UScoreGameInsSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__""));
} void UScoreGameInsSubsystem::Deinitialize()
{
Super::Deinitialize();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__""));
} int32 UScoreGameInsSubsystem::AddScore(int32 BaseScore)
{
Score = UKismetMathLibrary::Max(0, Score + BaseScore);
// 调用委托
ScoreChange.Broadcast(Score);
return Score;
}
用法二
支持创建抽象类,多个派生类,支持蓝图继承,支持遍历访问
- 注意UCLASS(Type)
- 父类为 Abstract抽象类,防止实例化
- Blueprintable 蓝图继承
- BlueprintType 可定义蓝图变量
- 注意每种类型的 Subsystem 只能创建一个实例
- 注意UCLASS(Type)
C++访问
- 访问单个
GetWorld()->GetGameInstance()->GetSubsystem<T>()
- 访问多个
GetWorld()->GetGameInstance()->GetSubsystemArray<T>()
- 访问单个
使用示例
/**
* 抽象类 USourceControlSubsystem
*/
UCLASS(Abstract, Blueprintable, BlueprintType)
class DESIGNPATTERNS_API USourceControlSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// ShouldCreateSubsystem 默认返回 True // 可重载
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "MySubsystem | SourceControl")
FString GetPlatformName();
}; /**
* 派生类 UGitSubsystem
*/
UCLASS()
class DESIGNPATTERNS_API UGitSubsystem : public USourceControlSubsystem
{
GENERATED_BODY()
public: // 初始化
virtual void Initialize(FSubsystemCollectionBase& Collection) override {
Super::Initialize(Collection);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"),*GetName());
} // 释放
virtual void Deinitialize() override {
Super::Deinitialize();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"),*GetName());
} virtual FString GetPlatformName_Implementation()override {
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【Git】"));
return TEXT("Git");
} void Help() {
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" Help Command"));
}
}; /**
* 派生类 USVNSubsystem
*/
UCLASS()
class DESIGNPATTERNS_API USVNSubsystem : public USourceControlSubsystem
{
GENERATED_BODY()
public: // 初始化
virtual void Initialize(FSubsystemCollectionBase& Collection) override {
Super::Initialize(Collection);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"), *GetName());
} // 释放
virtual void Deinitialize() override {
Super::Deinitialize();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"), *GetName());
} virtual FString GetPlatformName_Implementation()override {
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【SVN】"));
return TEXT("SVN");
}
}; /**
* 用于测试 ASourceControlActor
*/
UCLASS(Blueprintable, BlueprintType)
class DESIGNPATTERNS_API ASourceControlActor : public AActor
{
GENERATED_BODY()
public:
ASourceControlActor(){} virtual void BeginPlay()override {
Super::BeginPlay(); // 获取单个 Subsystem
UGitSubsystem* GitSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UGitSubsystem>();
GitSubsystem->Help(); // 获取多个 Subsystem,继承自同个抽象类
const TArray<USourceControlSubsystem*> SourceControlSubsystems = GetWorld()->GetGameInstance()->GetSubsystemArray<USourceControlSubsystem>();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"********************* 遍历USourceControlSubsystem ********************"));
for (USourceControlSubsystem* ItemSubSystem : SourceControlSubsystems)
{
ItemSubSystem->GetPlatformName();
}
}
};
蓝图调用
其他用法
支持Tick, 需继承 FTickableGameObject,参考 UAutoDestroySubsystem 写法
UCLASS()
class DESIGNPATTERNS_API UScoreGameInsSubsystem : public UGameInstanceSubsystem, public FTickableGameObject
{
GENERATED_BODY()
public: virtual void Tick(float DeltaTime)override{ UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"Ping Pong"));}
virtual bool IsTickable()const override { return !IsTemplate(); } //判断是否是 CDO,避免执行两次 Tick
virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UMyScoreSubsystem, STATGROUP_Tickables); }
};
支持 Subsystem 之间的访问
支持多个Subsystem定义依赖顺序,再初始化时调用
Collection.InitializeDependency(UScoreGameInsSubsystem::StaticClass());
UWorldSubsystem
可以再编辑器 和 运行时 使用
UCLASS()
class DESIGNPATTERNS_API UMyWorldSubsystem : public UWorldSubsystem
{
GENERATED_BODY()
public:
// 是否允许被创建
virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; }
// 初始化
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// 释放
virtual void Deinitialize() override; FString GetWorldType();
};
void UMyWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" [World] %-20s\t[Type] %s\t"), *GetWorld()->GetName(), *GetWorldType());
} void UMyWorldSubsystem::Deinitialize()
{
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" [World] %-20s\t[Type] %s\t"), *GetWorld()->GetName(), *GetWorldType());
} FString UMyWorldSubsystem::GetWorldType()
{
FString WorldTypeName;
switch (GetWorld()->WorldType) {
case EWorldType::None: {WorldTypeName = TEXT("None"); }break;
case EWorldType::Game: {WorldTypeName = TEXT("Game"); }break;
case EWorldType::Editor: {WorldTypeName = TEXT("Editor"); }break;
case EWorldType::PIE: {WorldTypeName = TEXT("PIE"); }break;
case EWorldType::EditorPreview: {WorldTypeName = TEXT("EditorPreview"); }break;
case EWorldType::GamePreview: {WorldTypeName = TEXT("GamePreview"); }break;
case EWorldType::GameRPC: {WorldTypeName = TEXT("GameRPC"); }break;
case EWorldType::Inactive: {WorldTypeName = TEXT("Inactive"); }break;
default: WorldTypeName = TEXT("default");
};
return WorldTypeName;
}
UEditorSubsystem
需要再 .build.cs 添加 EditorSubsystem 模块
if (Target.bBuildEditor)
{
PublicDependencyModuleNames.AddRange(new string[] { "EditorSubsystem" });
}
需要添加头文件
编译模式记得选Editor 模式
#include "EditorSubsystem.h"
UCLASS()
class DESIGNPATTERNS_API UMyEditorSubsystem : public UEditorSubsystem
{
GENERATED_BODY()
public: // 是否允许被创建
virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; }
// 初始化
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// 释放
virtual void Deinitialize() override; };
系统自带 Subsystem 使用蓝图示例
Asset Editor Subsystem
Asset Tag Subsystem
Editor Utility Subsystem
Layer Subsystem
Import Subsystem
Editor Validator Subsystem
检查、验证资产
从 EditorUtilityBlueprint 中选择父类 EditorValidatorBase
参考
- 编程子系统
- 【B站】[英文直播]Programming Subsystems(真实字幕组)
- 《InsideUE4》GamePlay架构(十一)Subsystems
- 【PPT】猫でも分かるUE4.22から入ったSubsystem
- Does 'Data Validation' not work for Blueprints?
附录
SubsystemCollection.cpp
- FSubsystemCollectionBase::Initialize()
- FSubsystemCollectionBase::AddAndInitializeSubsystem()
- FSubsystemCollectionBase::Deinitialize()
void FSubsystemCollectionBase::Initialize(UObject* NewOuter)
{
if (Outer != nullptr)
{
// already initialized
return;
} Outer = NewOuter;
check(Outer);
if (ensure(BaseType) && ensureMsgf(SubsystemMap.Num() == 0, TEXT("Currently don't support repopulation of Subsystem Collections.")))
{
check(!bPopulating); //Populating collections on multiple threads? if (SubsystemCollections.Num() == 0)
{
FSubsystemModuleWatcher::InitializeModuleWatcher();
} TGuardValue<bool> PopulatingGuard(bPopulating, true); if (BaseType->IsChildOf(UDynamicSubsystem::StaticClass())) // 判断是否是 UDynamicSubsystem 的子类
{
for (const TPair<FName, TArray<TSubclassOf<UDynamicSubsystem>>>& SubsystemClasses : DynamicSystemModuleMap)
{
for (const TSubclassOf<UDynamicSubsystem>& SubsystemClass : SubsystemClasses.Value)
{
if (SubsystemClass->IsChildOf(BaseType))
{
AddAndInitializeSubsystem(SubsystemClass);
}
}
}
}
else // 不是 UDynamicSubsystem 的子类
{
TArray<UClass*> SubsystemClasses;
GetDerivedClasses(BaseType, SubsystemClasses, true); for (UClass* SubsystemClass : SubsystemClasses)
{
AddAndInitializeSubsystem(SubsystemClass);
}
} // Statically track collections
SubsystemCollections.Add(this);
}
} void FSubsystemCollectionBase::Deinitialize()
{
// Remove static tracking
SubsystemCollections.Remove(this);
if (SubsystemCollections.Num() == 0)
{
FSubsystemModuleWatcher::DeinitializeModuleWatcher();
} // Deinit and clean up existing systems
SubsystemArrayMap.Empty();
for (auto Iter = SubsystemMap.CreateIterator(); Iter; ++Iter) // 遍历 SubsystemMap
{
UClass* KeyClass = Iter.Key();
USubsystem* Subsystem = Iter.Value();
if (Subsystem->GetClass() == KeyClass)
{
Subsystem->Deinitialize(); // 清理、释放
Subsystem->InternalOwningSubsystem = nullptr;
}
}
SubsystemMap.Empty();
Outer = nullptr;
} USubsystem* FSubsystemCollectionBase::AddAndInitializeSubsystem(UClass* SubsystemClass)
{
if (!SubsystemMap.Contains(SubsystemClass))
{
// Only add instances for non abstract Subsystems
if (SubsystemClass && !SubsystemClass->HasAllClassFlags(CLASS_Abstract))
{
// Catch any attempt to add a subsystem of the wrong type
checkf(SubsystemClass->IsChildOf(BaseType), TEXT("ClassType (%s) must be a subclass of BaseType(%s)."), *SubsystemClass->GetName(), *BaseType->GetName()); // Do not create instances of classes aren't authoritative
if (SubsystemClass->GetAuthoritativeClass() != SubsystemClass)
{
return nullptr;
} const USubsystem* CDO = SubsystemClass->GetDefaultObject<USubsystem>();
if (CDO->ShouldCreateSubsystem(Outer)) // 从CDO调用ShouldCreateSubsystem来判断是否要创建
{
USubsystem* Subsystem = NewObject<USubsystem>(Outer, SubsystemClass); //创建
SubsystemMap.Add(SubsystemClass,Subsystem); // 添加到 SubsystemMap
Subsystem->InternalOwningSubsystem = this;
Subsystem->Initialize(*this); //调用Initialize
return Subsystem;
}
}
return nullptr;
} return SubsystemMap.FindRef(SubsystemClass);
}
【UE4 C++】编程子系统 Subsystem的更多相关文章
- Windows - 子系统(subsystem)错误
Windows - 子系统(subsystem)错误 本文地址: http://blog.csdn.net/caroline_wendy VS2012生成错误: "error LNK2019 ...
- UE4蓝图编程的第一步
认识UE4蓝图中颜色与变量类型: UE4中各个颜色对应着不同的变量,连接点和连线的颜色都在表示此处是什么类型的变量.对于初学者来说一开始看到那么多连接点, 可能会很茫然,搞不清还怎么连,如果知道了颜色 ...
- UE4的编程C++创建一个FPSproject(两)角色网格、动画、HUD、子弹类
立即归还,本文将总结所有这些整理UE4有关角色的网络格.动画.子弹类HUD一个简单的实现. (五)角色加入网格 Character类为我们默认创建了一个SkeletaMeshComponent组件,所 ...
- UE4新手编程之创建C++项目
虚幻4中常用的按键和快捷键 虚幻4中有一些按键和快捷键很常用,牢记它们并运动到实际的项目开发中,将会大大地提高你的工作效率和使得工作更简便快捷.下面将列举它们出来: 按键 动作 鼠标左键 选 ...
- UE4新手编程之创建空白关卡和添加碰撞体
让我们接着上次继续学习UE4引擎,今天我们学习下怎样创建空白的关卡以及添加碰撞物体. 一. 创建空白关卡 1) 点击文件 -> 新建关卡(或者按快捷键Ctrl+N). 2) 你可以选择Defau ...
- ue4 C++ 编程 通过三个点的位置算出夹角
const FVector2D& Pt1 = 第一个点的位置; const FVector2D& Pt2 = 第二个点的位置; float EdgeRadians1 = FMath:: ...
- 【UE4 C++】学习笔记汇总
UE4 概念知识 基础概念--文件结构.类型.反射.编译.接口.垃圾回收.序列化[导图] GamePlay架构[导图] 类的继承层级关系[导图] 反射机制 垃圾回收机制/算法 序列化 Actor 的生 ...
- 【UE4】GamePlay架构
新标签打开或者下载看大图 更新: 增加 编程子系统 Subsystem 思维导图 Character pipeline
- UE4编程之C++创建一个FPS工程(一)创建模式&角色&处理输入
转自:http://blog.csdn.net/u011707076/article/details/44180951 从今天开始,我们一起来学习一下,如何使用C++将一个不带有任何初学者内容的空模板 ...
随机推荐
- CodeReview杂谈
豆皮粉儿们,大家好,又见面啦,今天由字节跳动的"躬冯"带来一个 code review 的故事. 作者:躬冯 2020年元旦假期到来的时候,孙总攒了个局,又把当年一起创造过屎山的咱 ...
- 【CSS】拼图验证练习
抄自B站Up主CodingStartup起码课 <!DOCTYPE html> <html lang="en"> <head> <meta ...
- 移动端ios上下滑动翻页事件失效
移动端开发过程中,在添加上下滑动事件时候,引入了最常用的移动端库zepto.js及其touch模块,有一种现象,安卓的手机没有问题,上下滑动翻页很正常 :但是到了ios上面,好啊,上下滑动会出现弹性滚 ...
- 【良心保姆级教程】java手把手教你用swing写一个学生的增删改查模块
很多刚入门的同学,不清楚如何用java.swing去开发出一个系统? 不清楚如何使用java代码去操作数据库进行增删改查一些列操作,不清楚java代码和数据库(mysql.sqlserver)之间怎么 ...
- Robot Framework(10)- 使用资源文件
如果你还想从头学起Robot Framework,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1770899.html 啥是资源文件 资 ...
- 快速模式第三包收尾之quick_inI2()
快速模式第三包收尾之quick_inI2() 文章目录 快速模式第三包收尾之quick_inI2() 1. 序言 2. quick_inI2()处理流程图 3. 报文格式 4. quick_inI2( ...
- ysoserial CommonsColletions4分析
ysoserial CommonsColletions4分析 其实CC4就是 CC3前半部分和CC2后半部分 拼接组成的,没有什么新的知识点. 不过要注意的是,CC4和CC2一样需要在commons- ...
- JAVA安全基础之代理模式(一)
JAVA安全基础之代理模式(一) 代理模式是java的一种很常用的设计模式,理解代理模式,在我们进行java代码审计时候是非常有帮助的. 静态代理 代理,或者称为 Proxy ,简单理解就是事情我不用 ...
- PHP中命名空间是怎样的存在(一)?
命名空间其实早在PHP5.3就已经出现了.不过大部分同学可能在各种框架的使用中才会接触到命名空间的内容,当然,现代化的开发也都离不开这些能够快速产出的框架.这次我们不从框架的角度,仅从简单的代码角度来 ...
- redis连接密码和指定数据库
台服务器上都快开启200个redis实例了,看着就崩溃了.这么做无非就是想让不同类型的数据属于不同的应用程序而彼此分开. 那么,redis有没有什么方法使不同的应用程序数据彼此分开同时又存储在相同的实 ...