0%

CSS选择器与优先级

选择器种类

  1. 元素选择器。 元素选择器以元素标签为选择子。对与html中标签类型形同的元素进行匹配。 因为一个html页面中,相同类型的元素出现不少,所以这类选择器匹配范围广泛,按照越少,越精准优先级越高的原则,元素选择器的优先级是很低的。
  2. 伪元素选择器。 伪元素选择器使用::作为前导,其一个明显特点是, 更改其选择对象不为前导的匹配对象,而是按照选择器规则去选取其他的对象。
  3. 类选择器。 类选择器以点号.为前导,后加类名。在html中,一簇类似功能的元素会指定class="classname ..."属性来为类选择器提供选择依据。在页面中,此类元素数量往往远少于元素数量,所以其优先级也更高。
  4. 伪类选择器。伪类选择器以冒号:作为前导,其后紧跟元素的某一状态。其选择特征为,在前导选择器选中的元素的基础上,添加某一个状态作为条件。
  5. ID选择器。以#作为前导,后紧跟ID名。按照规范,在一个html页面中,ID应当是唯一的,所以ID选择器要么补选中元素,要么选中一个。所以它的优先级是最高的。

选择器的优先级

在CSS属性附加到DOM树的过程中,往往会出现这样一种情况。多个选择器同时匹配到同一个DOM元素,而他们的属性配置值不同。 这个时候,就需要使用冲突解决方案了。

属性解决采用两类元素决定:

  1. 优先级。
  2. 先后顺序。

多个选择器匹配时,选择优先级高的优先级属性使用,当优先级相同时,选择后出现的属性。

浏览器如何计算优先级的

浏览器将优先级分为四个级别,采用级别记分,其中每个级别上符合条件的匹配将会使优先级得分累加,最终,采用优先级得分最高的样式。

  1. 第一级别:内联。在html元素中配置style属性时,为内联属性,而且它不属于选择器,且只能配置一次,所以优先级最高。 ** 需要注意的是,使用内联属性不是一个好习惯,除非不得已我们尽量使用外部CSS来确定样式 **
  2. 第二级别: ID选择器。 CSS中,使用井号前导一个元素的ID选择器。它能够精准的选定某一个元素,所以优先级很高,在这里它是次高的优先级。
  3. 第三级别:类/伪类选择器。
  4. 第四级别:元素/伪元素选择器。

其中,CSS样式表选择器每匹配到一个规则,就在规则计分项上记一分,最终,从第一优先级向第四优先级方向对比,当第一个积分不等的优先级出现后即停止,选中计分高的选择器。

特殊的优先级 !important

!important 拥有至高的优先级, 它用于描述某一条属性,而非一个选择器。它的工作方式打破了选择器优先级的常规,一旦某个属性被声明为 !important,则无视比它所在选择器更高优先级的选择器,对于该项属性进行强制覆盖。

按照规范,这个特性是应该谨慎使用的,一般的,除非没有其他办法可以打到这个效果,所有情况下都不推荐使用。

列表选择器

有一种特殊的选择器写法, 它使用逗号,分隔选择表达式, 例如:

1
2
3
h, div {
background-color: red;
}

这类选择器与分开写法是等效的,等效于:

1
2
3
4
5
6
7
h {
background-color: red;
}

div {
background-color: red;
}

但是,一旦其中一个选择器出错,分开写的那个选择器会被丢弃不处理,而列表选择器则整个失效。

CSS位置对效果的影响

考虑到一些特殊用户的偏好(例如视力弱者希望字体更大),需要满足以用户指定的样式来覆盖开发者指定的样式。

CSS的位置基本可以分为:

  1. 代理内置的默认样式,即浏览器默认样式。若开发者和用户均未指定样式时,则采用这个默认样式渲染。
  2. 用户样式,一般在浏览器设置中,允许用户更改某些浏览器内置的样式。 这个样式,就是用户样式。
  3. 开发者常规样式。 即一般开发人员使用CSS声明的样式。
  4. 开发者 !important样式。 开发者使用!important声明的样式。
  5. 用户!important样式。类似开发者!important样式。

以上积累样式,如果出现冲突,那么序号大者将会覆盖序号小者的设置。

RSA 非对称加密算法原理

欧拉函数

在数论中,对正整数n,欧拉函数 \(\varphi(n)\) 是小于或等于n的正整数中与n互质的数的数目。此函数以其首名研究者欧拉命名,它又称为φ函数(由高斯所命名)或是欧拉总计函数(totient function,由西尔维斯特所命名)。 欧拉函数

RSA 算法的核心: 欧拉定理

在数论中,欧拉定理(也称费马-欧拉定理或欧拉 \({\varphi}\) 函数定理)是一个关于同余的性质。欧拉定理表明,若 n,a为正整数,且 n,a 互素(即 \(\gcd(a,n)=1\) ),则

\[ a^{\phi{\left(n\right)}} \equiv 1 \pmod n \]

欧拉定理

费马小定理: 欧拉定理的特例

费马定理的表达式 :

\[ a^{\phi{\left(n\right)}} \equiv 1 \pmod n \]

在特殊情况下, 当n为质数时, \(\varphi(n)=n-1\) , 此时, 欧拉定理的表述即为:

\[ a^{n-1} \equiv 1\pmod n \]

这个公式即为就是费马小定理。

模反元素

如果两个正整数 a 和 n 互质, 那么一定存在一个整数 b 满足:

\[ ab \equiv 1\pmod n \]

即, (ab - 1)能被n整除。 b就叫做a的模反元素。 显然的, 模反元素并不唯一, 对于模反元素b, 对于所有的正整数 \(m=b+kn\) 也为a的模反元素。

模反元素必然存在

对费马定理进行变形:

\[ a^{\phi\left({n}\right)}\equiv a\times a^{\phi\left(n\right)-1}\equiv 1 \pmod n \]

在此变形中, \(a^{\phi\left(n\right)-1}\) 即为a的模反元素。

RSA 基本概念

我们约定:

  • RSA加密秘钥为E, 它是一个大整数。
  • RSA解密秘钥为D, 它是一个大整数。
  • RSA加密和解密需要用的模数N, 它是一个大整数。
RSA 加密过程

\[ 密文 = 明文^E mod N \]

RSA 加密过程就是这么简单, 它对明文做一个E次幂运算, 然后对N取模。 在这个过程中, E和N的组合就是公钥

RSA 解密过程

对应的, RSA的解密过程也非常简单:

\[ 明文 = 密文^D mod N \]

RSA 的加密和解密过程完全一致, 只是它们换了一个秘钥, 进行了完全一样的运算。 在这个过程中, D和N的组合就是私钥

非对称秘钥对的生成

在基本概念中, 约定了几个量, E, D, N 在这里,我么继续沿用, 额外的,在生成密钥对的过程中, 引入一个新的量L, 它只用于生成密钥对, 但是最终结果中我们并不体现它。

1. 求N

准备两个大质数p, q。(p和q要最够大)

\[ N=p \times q \]

2. 求L

L是 p-1 和 q-1 的最小公倍数。

\[ L=lcm(p-1, q-1) \]

实际上, 理论公式上, L应为ϕ(n), 这样后面的一系列推倒便是欧拉定理的应用了, 但是在实际中,它可以化简为更小的最小公倍数L, 这里,我们使用了实际中的化简方案

3. 求E

E是一个比1大, 比L小的数字,且E和L要互质, 即 E和L的最大公约数为1, 表达为数学语言为。

\[ \begin{eqnarray*} 1 < E < L \\ gcd(E, L) = 1 \end{eqnarray*} \]

计算E的过程我们采用随机穷举的模式, 使用伪随机数生成器, 随机生成一个1和L之间的数字, 判断是否满足它与L互质, 如果不满足, 则重复生成并判断下一个随机数。

至此, RSA的公钥部分E,N我们已经得到了, 公钥: (E, N)。

我们加入E和L互质的这个条件,是为了满足我们需要的解密参数D一定存在

4. 求D

\[ \begin{eqnarray*} 1 < D < L \\\\ E \times D\ mod\ L = 1 \end{eqnarray*} \]

按照上述的限制条件, 计算出D, 至此, 私钥部分也计算完成, 私钥为(D, N)。

Log4J使用外部配置

使用 Java语言开发的程序员和爱好者们应当对log4j并不陌生。 它是一个Java的日志系统实现。log4j主页

解决方案

先上解决方案一便以后查阅。

  1. 更改位置需要放在Main类的静态初始化块最开始的位置。
  2. 两个方式:
    • 配置系统变量log4j.defaultInitOveride。此方法要比较繁琐,且对用户行为不可控,不推荐这种。
    • 在代码中使用System.setProperty("log4j.defaultInitOverride", "true") 配置变量。这个接口在初始时调用,完全程序员指定,即使外部有配置也可覆盖,所以推荐这种方式来避免用户的奇葩行为。
  3. 调用 PropertyConfigurator.configure接口指定外部配置的位置。

背景

最近做一个决策服务器,为了跟踪调试我需要大量的Debug级别的日志,这台服务器在部署生产环境时则不需要这么多日志。我决定让用户使用外部自己的日志配置来定义日志,如果用户没有指定自定义的日志配置,那么就使用默认内建的日志配置。这样也方便随时更改日志而无需重新打包(默认的配置会被打进jar包)。

踩坑过程

如果不做额外的操作, 那么log4j的配置文件默认去代码目录寻找配置文件。 经过谷歌搜了一下如何使用自定义配置文件,得到了一个方法:

1
PropertyConfigurator.configure();

我只使用了Configurator.configure 来指定了外部配置文件后发现,只是外部的配置文件替换了默认配置的配置,但是没有删除外部配置的Logger。明显与预期不符。

于是开始阅读 Configurator.configure 的实现代码。以下是adoptOpenJdk14版本的源码:

1
2
3
4
5
6
7
static
public
void configure(String configFilename) {
new PropertyConfigurator().doConfigure(configFilename,
LogManager.getLoggerRepository());
}

我发现,接口的最后是用LogManager.getLoggerRepository()。

如果使用默认位置配置的话,那么是无需指定配置的,可以直接通过 LogManager.getLogger() 来获取logger,由此不难想到,默认配置的加载过程

  • 要么是在LogManager的静态初始化块中完成。
  • 要么是在getLogger()初次调用时完成。

首先检查LogManager的初始化代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 static {
// By default we use a DefaultRepositorySelector which always returns 'h'.
Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
repositorySelector = new DefaultRepositorySelector(h);

/** Search for the properties file log4j.properties in the CLASSPATH. */
String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,
null);

// if there is no default init override, then get the resource
// specified by the user or the default config file.
if(override == null || "false".equalsIgnoreCase(override)) {

String configurationOptionStr = OptionConverter.getSystemProperty(
DEFAULT_CONFIGURATION_KEY,
null);

String configuratorClassName = OptionConverter.getSystemProperty(
CONFIGURATOR_CLASS_KEY,
null);

URL url = null;

// if the user has not specified the log4j.configuration
// property, we search first for the file "log4j.xml" and then
// "log4j.properties"
if(configurationOptionStr == null) {
url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
if(url == null) {
url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
}
} else {
try {
url = new URL(configurationOptionStr);
} catch (MalformedURLException ex) {
// so, resource is not a URL:
// attempt to get the resource from the class path
url = Loader.getResource(configurationOptionStr);

//////////////////////Ignore Code ////////////////
}
}
}

其中,第12行代码发现,若DEFAULT_INIT_OVERRIDE_KEY这个环境变量被配置且不为"false",就会执行加载默认配置的过程。那我们想要log4j不加载默认配置,只需要将DEFAULT_INIT_OVERRIDE_KEY 变量设置为一个不为"false"的值即可达到目的,这里我使用的是"true"。

1
2
3
4
5
6
7
/**
* @deprecated This variable is for internal use only. It will
* become private in future versions.
*/
public static final String DEFAULT_INIT_OVERRIDE_KEY =
"log4j.defaultInitOverride";

DEFAULT_INIT_OVERRIDE_KEY 定义的是log4j.defaultInitOverride所以,只需要:

  • 在操作系统变量配置 log4j.defaultInitOverride=true
  • 或者虚拟机启动参数 -Dlog4j.defaultInitOverride=true
  • 或者在LogManager类被初始化前的位置调用Java代码System.setProperty("log4j.defaultInitOverride", "true")

这三个操作任选其一即可。

结合我的需求,我不想让用户有太多干扰能力,也降低用户的使用学习成本,选择了最后一个方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {
static {
initLog4j();
}

//other static field

public static void main(String[] args) throws Exception {
//ServerStart Code
}

private static void initLog4j() {
//check configuration file
Path configurationPath = Paths.get("cfg", "log4j.properties");
if (!Files.exists(configurationPath) || !Files.isRegularFile(configurationPath)) {
return;
}

try {
PropertyConfigurator.configure(configurationPath.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}

额外一些说明

为什么要把initLog4j方法调用放在Main类的静态初始化块中且放在初始化块最前面?

这牵扯到Java虚拟机类加载的机制。

类加载各个过程严格按照图中所示顺序启动。但是它们并不要求按要求结束,但这个不重要。这里重要的是在一个类的相关功能要能使用前,必然会先完成初始化。

其中,我们类的静态代码块在初始化过程中执行。它在类使用前执行完。

那问题就归结在了何时会触发类的加载,这里指的是org.apache.log4j.LogManager首次加载。

按照jvm规范,当执行以下四个指令时,触发类加载:

  • new
  • getstatic
  • putstatic
  • invokestatic

其中 getstatic 和 putstatic 两个执行分别是读写类的静态成员产生,而invokestatic指令在调用类的静态方法时产生。 而这些语句,都可以在出现在类的静态初始化块中。所以,Main类的静态初始化块就很有可能使用别的类的静态域,它又可能使用其他类的静态域,这将会构成一个复杂的依赖链。我们很难保持,或者很难长期保持我们的initLog4j方法总在这些之前执行,所以,我们把它放到了一个最稳妥的地方,在Main类最上方,使用一个静态块来抢先完成配置。

UE4 使用ProceduralMeshComponent运行时显示动态物体的坑

主要使用的接口

1
2
3
4
5
6
7
8
9
void CreateMeshSection(
int32 SectionIndex,
const TArray<FVector>& Vertices,
const TArray<int32>& Triangles,
const TArray<FVector>& Normals,
const TArray<FVector2D>& UV0,
const TArray<FColor>& VertexColors,
const TArray<FProcMeshTangent>& Tangents,
bool bCreateCollision)

首先遇到的问题

  • 无法导入"ProceduralMeshComponent.h" 头文件, 明明我可以找到这个文件,但是却无法导入。

查阅官方文档获得答案,则是个实验性功能, 需要.build.cs 文件中添加对模块的依赖。

创建工程后的默认构建文件内容:

1
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

这里我的项目名字是ProceduralMeshDemo, 所以构建文件在VS中的路径 Games/Source/ProceduralMeshDemo/ProceduralMeshDemo.build.cs

在依赖中添加 "ProceduralMeshComponent", 最终内容为:

1
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "ProceduralMeshComponent" });

按照官方文档的说法,我需要再在 <ProjectName> 中添加插件配置, 官方文档较旧,我发现我没有那么做,插件依然可见可用了。我的UE4版本是4.24.3,所以,至少这个版本之后就可以不用这个步骤了。

接下来的问题

  • 成功创建了Mesh并展示出来了,但是VertexColor没有生效,模型没有颜色。

这个问题我没有找到官方的文档解决方式, 网上资料也了了少的可怜,只有少数朋友使用ProceduralMeshComponent来动态创建模型,但是并不对模型进行着色。最终在问答区找到了一个还比较贴切的问题,有大佬给了个含糊的回答。

Creating a material with "vertex colors" node plugged into the base color slot, > then using

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
mesh->SetMaterial(2, Cast<UMaterial>(StaticLoadObject(UMaterial::StaticClass(), NULL, *FString(UISC::florMTLoc + materials[0]))));  ```

Be sure to set the material index to match that of the element index of your procedural mesh. In my case it was indexs 1 and 2 (which is why it didn't show before because I used set material with index 0).


原答案地址: [How can i see vertex colors on procedural mesh?](https://answers.unrealengine.com/questions/395390/how-can-i-see-vertex-colors-on-procedural-mesh.html)

意思是要使用要给Vertex Clolors 作为基色的材质才行。

于是创建一个材质:

![][VertexColorMaterial]

贴一下构建平面的关键代码。
```cpp
const auto RowCount = 5;
const auto ColCount = 5;
//Construct Vertices
TArray<FVector> Vertices;
TArray<FColor> VertColors;
for (auto Row = 0; Row < RowCount; ++Row)
{
for (auto Col = 0; Col < ColCount; ++Col)
{
Vertices.Add(FVector(Row*10, Col*10, 10));
VertColors.Add(FColor(Row*40, Col*40, 100));
}
}

//Construct Triangles
TArray<int32> TriangleIndexes;
for (auto Row = 0; Row < RowCount-1; ++Row)
{
for (auto Col = 0; Col < ColCount-1; ++Col)
{
TriangleIndexes.Add(Row * ColCount + Col);
TriangleIndexes.Add(Row * ColCount + Col + 1);
TriangleIndexes.Add((Row + 1) * ColCount + Col);

TriangleIndexes.Add((Row + 1) * ColCount + Col);
TriangleIndexes.Add(Row * ColCount + Col + 1);
TriangleIndexes.Add((Row + 1 )* ColCount + Col + 1);
}
}

Mesh->CreateMeshSection(0, Vertices, TriangleIndexes, TArray<FVector>(), TArray<FVector2D>(), VertColors, TArray<FProcMeshTangent>(), false);
Mesh->SetMaterial(0, Material);

至此,终于初见效果了:

生活处处皆是坑

本以为是守得云开见月明, 明月就闪了一下。一段优化之后,莫名奇妙的出现了问题,Demo是在BeginPlay事件中创建的。莫名奇妙的Tick函数就不调了,九牛二虎没找到原因,只好重启了一次UE4Editor,莫名其妙就好了。

  • 优化过后在Tick中清理并创建后, 无法渲染出平面了。

一番折腾后发现,在CreateSection / UpdateSection 调用后,必须再次SetMaterial,即使材质从未改变。。。

有点心累, 先记到这里。


背景

Windows10下 WSL(Ubuntu), 之前使用没有注意过,今天需要写个很长的编译命令,写错了之后想修改,发现光标不显示,但是能正常写入和删除字符。目测只是光标的问题。

解决方案

谷歌搜索来的方法,试了一下,it's works!!!. 我没搞懂其中的原理,但是确实好用了。记入笔记以收藏。

1
2
3
4
5
#显示光标
echo -e "\033[?25h"

#隐藏光标
echo -e "\033[?25l"

感谢sparkydogX的解决方案