feat: arrangement 新增冲突、冲突处理函数、约束处理函数
This commit is contained in:
parent
978777e36c
commit
84f36eaaba
|
@ -7,6 +7,7 @@ type Area[ID comparable, AreaInfo any] struct {
|
||||||
info AreaInfo
|
info AreaInfo
|
||||||
items map[ID]Item[ID]
|
items map[ID]Item[ID]
|
||||||
constraints []AreaConstraintHandle[ID, AreaInfo]
|
constraints []AreaConstraintHandle[ID, AreaInfo]
|
||||||
|
conflicts []AreaConflictHandle[ID, AreaInfo]
|
||||||
evaluate AreaEvaluateHandle[ID, AreaInfo]
|
evaluate AreaEvaluateHandle[ID, AreaInfo]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,13 +22,55 @@ func (slf *Area[ID, AreaInfo]) GetItems() map[ID]Item[ID] {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllow 检测一个成员是否可以被添加到该编排区域中
|
// IsAllow 检测一个成员是否可以被添加到该编排区域中
|
||||||
func (slf *Area[ID, AreaInfo]) IsAllow(item Item[ID]) (Item[ID], bool) {
|
func (slf *Area[ID, AreaInfo]) IsAllow(item Item[ID]) (constraintErr error, conflictItems map[ID]Item[ID], allow bool) {
|
||||||
for _, constraint := range slf.constraints {
|
for _, constraint := range slf.constraints {
|
||||||
if item, allow := constraint(slf, item); !allow {
|
if err := constraint(slf, item); err != nil {
|
||||||
return item, false
|
return err, nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, true
|
for _, conflict := range slf.conflicts {
|
||||||
|
if items := conflict(slf, item); len(items) > 0 {
|
||||||
|
if conflictItems == nil {
|
||||||
|
conflictItems = make(map[ID]Item[ID])
|
||||||
|
}
|
||||||
|
for id, item := range items {
|
||||||
|
conflictItems[id] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, conflictItems, len(conflictItems) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsConflict 检测一个成员是否会造成冲突
|
||||||
|
func (slf *Area[ID, AreaInfo]) IsConflict(item Item[ID]) bool {
|
||||||
|
if hash.Exist(slf.items, item.GetID()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, conflict := range slf.conflicts {
|
||||||
|
if items := conflict(slf, item); len(items) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConflictItems 获取与一个成员产生冲突的所有其他成员
|
||||||
|
func (slf *Area[ID, AreaInfo]) GetConflictItems(item Item[ID]) map[ID]Item[ID] {
|
||||||
|
if hash.Exist(slf.items, item.GetID()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var conflictItems map[ID]Item[ID]
|
||||||
|
for _, conflict := range slf.conflicts {
|
||||||
|
if items := conflict(slf, item); len(items) > 0 {
|
||||||
|
if conflictItems == nil {
|
||||||
|
conflictItems = make(map[ID]Item[ID])
|
||||||
|
}
|
||||||
|
for id, item := range items {
|
||||||
|
conflictItems[id] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conflictItems
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetScore 获取该编排区域的评估分数
|
// GetScore 获取该编排区域的评估分数
|
||||||
|
|
|
@ -4,18 +4,29 @@ package arrangement
|
||||||
type AreaOption[ID comparable, AreaInfo any] func(area *Area[ID, AreaInfo])
|
type AreaOption[ID comparable, AreaInfo any] func(area *Area[ID, AreaInfo])
|
||||||
|
|
||||||
type (
|
type (
|
||||||
AreaConstraintHandle[ID comparable, AreaInfo any] func(area *Area[ID, AreaInfo], item Item[ID]) (Item[ID], bool)
|
AreaConstraintHandle[ID comparable, AreaInfo any] func(area *Area[ID, AreaInfo], item Item[ID]) error
|
||||||
|
AreaConflictHandle[ID comparable, AreaInfo any] func(area *Area[ID, AreaInfo], item Item[ID]) map[ID]Item[ID]
|
||||||
AreaEvaluateHandle[ID comparable, AreaInfo any] func(areaInfo AreaInfo, items map[ID]Item[ID]) float64
|
AreaEvaluateHandle[ID comparable, AreaInfo any] func(areaInfo AreaInfo, items map[ID]Item[ID]) float64
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithAreaConstraint 设置编排区域的约束条件
|
// WithAreaConstraint 设置编排区域的约束条件
|
||||||
// - 该约束用于判断一个成员是否可以被添加到该编排区域中
|
// - 该约束用于判断一个成员是否可以被添加到该编排区域中
|
||||||
|
// - 与 WithAreaConflict 不同的是,约束通常用于非成员关系导致的硬性约束,例如:成员的等级过滤、成员的性别等
|
||||||
func WithAreaConstraint[ID comparable, AreaInfo any](constraint AreaConstraintHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] {
|
func WithAreaConstraint[ID comparable, AreaInfo any](constraint AreaConstraintHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] {
|
||||||
return func(area *Area[ID, AreaInfo]) {
|
return func(area *Area[ID, AreaInfo]) {
|
||||||
area.constraints = append(area.constraints, constraint)
|
area.constraints = append(area.constraints, constraint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAreaConflict 设置编排区域的冲突条件,冲突处理函数需要返回造成冲突的成员列表
|
||||||
|
// - 该冲突用于判断一个成员是否可以被添加到该编排区域中
|
||||||
|
// - 与 WithAreaConstraint 不同的是,冲突通常用于成员关系导致的软性约束,例如:成员的职业唯一性、成员的种族唯一性等
|
||||||
|
func WithAreaConflict[ID comparable, AreaInfo any](conflict AreaConflictHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] {
|
||||||
|
return func(area *Area[ID, AreaInfo]) {
|
||||||
|
area.conflicts = append(area.conflicts, conflict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithAreaEvaluate 设置编排区域的评估函数
|
// WithAreaEvaluate 设置编排区域的评估函数
|
||||||
// - 该评估函数将影响成员被编入区域的优先级
|
// - 该评估函数将影响成员被编入区域的优先级
|
||||||
func WithAreaEvaluate[ID comparable, AreaInfo any](evaluate AreaEvaluateHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] {
|
func WithAreaEvaluate[ID comparable, AreaInfo any](evaluate AreaEvaluateHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] {
|
||||||
|
|
|
@ -23,10 +23,14 @@ func NewArrangement[ID comparable, AreaInfo any](options ...Option[ID, AreaInfo]
|
||||||
// - 目前我能想到的用途只有我的过往经历:排课
|
// - 目前我能想到的用途只有我的过往经历:排课
|
||||||
// - 如果是在游戏领域,或许适用于多人小队匹配编排等类似情况
|
// - 如果是在游戏领域,或许适用于多人小队匹配编排等类似情况
|
||||||
type Arrangement[ID comparable, AreaInfo any] struct {
|
type Arrangement[ID comparable, AreaInfo any] struct {
|
||||||
areas []*Area[ID, AreaInfo] // 所有的编排区域
|
areas []*Area[ID, AreaInfo] // 所有的编排区域
|
||||||
items map[ID]Item[ID] // 所有的成员
|
items map[ID]Item[ID] // 所有的成员
|
||||||
fixed map[ID]ItemFixedAreaHandle[AreaInfo] // 固定编排区域的成员
|
fixed map[ID]ItemFixedAreaHandle[AreaInfo] // 固定编排区域的成员
|
||||||
priority map[ID][]ItemPriorityHandle[ID, AreaInfo] // 成员的优先级函数
|
priority map[ID][]ItemPriorityHandle[ID, AreaInfo] // 成员的优先级函数
|
||||||
|
threshold int // 重试次数阈值
|
||||||
|
|
||||||
|
constraintHandles []ConstraintHandle[ID, AreaInfo]
|
||||||
|
conflictHandles []ConflictHandle[ID, AreaInfo]
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddArea 添加一个编排区域
|
// AddArea 添加一个编排区域
|
||||||
|
@ -47,13 +51,11 @@ func (slf *Arrangement[ID, AreaInfo]) AddItem(item Item[ID]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arrange 编排
|
// Arrange 编排
|
||||||
func (slf *Arrangement[ID, AreaInfo]) Arrange(threshold int) (areas []*Area[ID, AreaInfo], noSolution map[ID]Item[ID]) {
|
func (slf *Arrangement[ID, AreaInfo]) Arrange() (areas []*Area[ID, AreaInfo], noSolution map[ID]Item[ID]) {
|
||||||
if len(slf.areas) == 0 {
|
if len(slf.areas) == 0 {
|
||||||
return slf.areas, slf.items
|
return slf.areas, slf.items
|
||||||
}
|
}
|
||||||
if threshold <= 0 {
|
|
||||||
threshold = 10
|
|
||||||
}
|
|
||||||
var items = hash.Copy(slf.items)
|
var items = hash.Copy(slf.items)
|
||||||
var fixed = hash.Copy(slf.fixed)
|
var fixed = hash.Copy(slf.fixed)
|
||||||
|
|
||||||
|
@ -87,24 +89,26 @@ func (slf *Arrangement[ID, AreaInfo]) Arrange(threshold int) (areas []*Area[ID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var pending = hash.ToSlice(items)
|
var editor = &Editor[ID, AreaInfo]{
|
||||||
sort.Slice(pending, func(i, j int) bool {
|
a: slf,
|
||||||
return priorityInfo[pending[i].GetID()] > priorityInfo[pending[j].GetID()]
|
pending: hash.ToSlice(items),
|
||||||
|
falls: map[ID]struct{}{},
|
||||||
|
}
|
||||||
|
sort.Slice(editor.pending, func(i, j int) bool {
|
||||||
|
return priorityInfo[editor.pending[i].GetID()] > priorityInfo[editor.pending[j].GetID()]
|
||||||
})
|
})
|
||||||
|
|
||||||
var current Item[ID]
|
var current Item[ID]
|
||||||
var fails []Item[ID]
|
for editor.GetPendingCount() > 0 {
|
||||||
var retryCount = 0
|
current = editor.pending[0]
|
||||||
for len(pending) > 0 {
|
editor.pending = editor.pending[1:]
|
||||||
current = pending[0]
|
|
||||||
pending = pending[1:]
|
|
||||||
|
|
||||||
var maxPriority = float64(0)
|
var maxPriority = float64(0)
|
||||||
var area *Area[ID, AreaInfo]
|
var area *Area[ID, AreaInfo]
|
||||||
for areaIndex, priority := range itemAreaPriority[current.GetID()] {
|
for areaIndex, priority := range itemAreaPriority[current.GetID()] {
|
||||||
if priority > maxPriority {
|
if priority > maxPriority {
|
||||||
a := slf.areas[areaIndex]
|
a := slf.areas[areaIndex]
|
||||||
if _, allow := a.IsAllow(current); allow {
|
if slf.try(editor, a, current) {
|
||||||
maxPriority = priority
|
maxPriority = priority
|
||||||
area = a
|
area = a
|
||||||
}
|
}
|
||||||
|
@ -116,33 +120,33 @@ func (slf *Arrangement[ID, AreaInfo]) Arrange(threshold int) (areas []*Area[ID,
|
||||||
if _, exist := itemAreaPriority[current.GetID()][i]; exist {
|
if _, exist := itemAreaPriority[current.GetID()][i]; exist {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, allow := a.IsAllow(current); allow {
|
if slf.try(editor, a, current) {
|
||||||
area = a
|
area = a
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if area == nil {
|
if area == nil {
|
||||||
fails = append(fails, current)
|
editor.fails = append(editor.fails, current)
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
area.items[current.GetID()] = current
|
area.items[current.GetID()] = current
|
||||||
|
editor.falls[current.GetID()] = struct{}{}
|
||||||
|
|
||||||
end:
|
end:
|
||||||
{
|
{
|
||||||
if len(fails) > 0 {
|
if len(editor.fails) > 0 {
|
||||||
noSolution = map[ID]Item[ID]{}
|
noSolution = map[ID]Item[ID]{}
|
||||||
for _, item := range fails {
|
for _, item := range editor.fails {
|
||||||
noSolution[item.GetID()] = item
|
noSolution[item.GetID()] = item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pending) == 0 && len(fails) > 0 {
|
if len(editor.pending) == 0 && len(editor.fails) > 0 {
|
||||||
pending = fails
|
editor.pending = editor.fails
|
||||||
fails = fails[:0]
|
editor.fails = editor.fails[:0]
|
||||||
retryCount++
|
editor.retryCount++
|
||||||
if retryCount > threshold {
|
if editor.retryCount > slf.threshold {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,3 +155,43 @@ func (slf *Arrangement[ID, AreaInfo]) Arrange(threshold int) (areas []*Area[ID,
|
||||||
|
|
||||||
return slf.areas, noSolution
|
return slf.areas, noSolution
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try 尝试将 current 编排到 a 中
|
||||||
|
func (slf *Arrangement[ID, AreaInfo]) try(editor *Editor[ID, AreaInfo], a *Area[ID, AreaInfo], current Item[ID]) bool {
|
||||||
|
err, conflictItems, allow := a.IsAllow(current)
|
||||||
|
if !allow {
|
||||||
|
if err != nil {
|
||||||
|
var solve = err
|
||||||
|
for _, handle := range slf.constraintHandles {
|
||||||
|
if solve = handle(editor, a, current, solve); solve == nil {
|
||||||
|
err, conflictItems, allow = a.IsAllow(current)
|
||||||
|
if allow {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
// 当 err 依旧不为 nil 时,发生约束处理函数欺骗行为,不做任何处理
|
||||||
|
if len(conflictItems) > 0 {
|
||||||
|
goto conflictHandle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conflictHandle:
|
||||||
|
{
|
||||||
|
if err == nil && len(conflictItems) > 0 { // 硬性约束未解决时,不考虑冲突解决
|
||||||
|
var solve = conflictItems
|
||||||
|
for _, handle := range slf.conflictHandles {
|
||||||
|
if solve = handle(editor, a, current, solve); len(solve) == 0 {
|
||||||
|
if a.IsConflict(current) {
|
||||||
|
allow = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 依旧存在冲突时,表明冲突处理函数存在欺骗行为,不做任何处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allow
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package arrangement_test
|
package arrangement_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kercylan98/minotaur/utils/arrangement"
|
"github.com/kercylan98/minotaur/utils/arrangement"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -24,14 +25,23 @@ type Team struct {
|
||||||
|
|
||||||
func TestArrangement_Arrange(t *testing.T) {
|
func TestArrangement_Arrange(t *testing.T) {
|
||||||
var a = arrangement.NewArrangement[int, *Team]()
|
var a = arrangement.NewArrangement[int, *Team]()
|
||||||
a.AddArea(&Team{ID: 1}, arrangement.WithAreaConstraint[int, *Team](func(area *arrangement.Area[int, *Team], item arrangement.Item[int]) (arrangement.Item[int], bool) {
|
a.AddArea(&Team{ID: 1}, arrangement.WithAreaConstraint[int, *Team](func(area *arrangement.Area[int, *Team], item arrangement.Item[int]) error {
|
||||||
return nil, len(area.GetItems()) < 2
|
if len(area.GetItems()) >= 2 {
|
||||||
|
return errors.New("too many")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}))
|
}))
|
||||||
a.AddArea(&Team{ID: 2}, arrangement.WithAreaConstraint[int, *Team](func(area *arrangement.Area[int, *Team], item arrangement.Item[int]) (arrangement.Item[int], bool) {
|
a.AddArea(&Team{ID: 2}, arrangement.WithAreaConstraint[int, *Team](func(area *arrangement.Area[int, *Team], item arrangement.Item[int]) error {
|
||||||
return nil, len(area.GetItems()) < 1
|
if len(area.GetItems()) >= 1 {
|
||||||
|
return errors.New("too many")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}))
|
}))
|
||||||
a.AddArea(&Team{ID: 3}, arrangement.WithAreaConstraint[int, *Team](func(area *arrangement.Area[int, *Team], item arrangement.Item[int]) (arrangement.Item[int], bool) {
|
a.AddArea(&Team{ID: 3}, arrangement.WithAreaConstraint[int, *Team](func(area *arrangement.Area[int, *Team], item arrangement.Item[int]) error {
|
||||||
return nil, len(area.GetItems()) < 2
|
if len(area.GetItems()) >= 2 {
|
||||||
|
return errors.New("too many")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}))
|
}))
|
||||||
//a.AddArea(&Team{ID: 3})
|
//a.AddArea(&Team{ID: 3})
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package arrangement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/hash"
|
||||||
|
"github.com/kercylan98/minotaur/utils/slice"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Editor 编排器
|
||||||
|
type Editor[ID comparable, AreaInfo any] struct {
|
||||||
|
a *Arrangement[ID, AreaInfo]
|
||||||
|
pending []Item[ID]
|
||||||
|
fails []Item[ID]
|
||||||
|
falls map[ID]struct{}
|
||||||
|
retryCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPendingCount 获取待编排的成员数量
|
||||||
|
func (slf *Editor[ID, AreaInfo]) GetPendingCount() int {
|
||||||
|
return len(slf.pending)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAreaItem 从编排区域中移除一个成员到待编排队列中,如果该成员不存在于编排区域中,则不进行任何操作
|
||||||
|
func (slf *Editor[ID, AreaInfo]) RemoveAreaItem(area *Area[ID, AreaInfo], item Item[ID]) {
|
||||||
|
target := area.items[item.GetID()]
|
||||||
|
if target == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(area.items, item.GetID())
|
||||||
|
delete(slf.falls, item.GetID())
|
||||||
|
slf.pending = append(slf.pending, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAreaItem 将一个成员添加到编排区域中,如果该成员已经存在于编排区域中,则不进行任何操作
|
||||||
|
func (slf *Editor[ID, AreaInfo]) AddAreaItem(area *Area[ID, AreaInfo], item Item[ID]) {
|
||||||
|
if hash.Exist(slf.falls, item.GetID()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
area.items[item.GetID()] = item
|
||||||
|
slf.falls[item.GetID()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAreas 获取所有的编排区域
|
||||||
|
func (slf *Editor[ID, AreaInfo]) GetAreas() []*Area[ID, AreaInfo] {
|
||||||
|
return slice.Copy(slf.a.areas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRetryCount 获取重试次数
|
||||||
|
func (slf *Editor[ID, AreaInfo]) GetRetryCount() int {
|
||||||
|
return slf.retryCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetThresholdProgressRate 获取重试次数阈值进度
|
||||||
|
func (slf *Editor[ID, AreaInfo]) GetThresholdProgressRate() float64 {
|
||||||
|
return float64(slf.retryCount) / float64(slf.a.threshold)
|
||||||
|
}
|
|
@ -2,3 +2,42 @@ package arrangement
|
||||||
|
|
||||||
// Option 编排选项
|
// Option 编排选项
|
||||||
type Option[ID comparable, AreaInfo any] func(arrangement *Arrangement[ID, AreaInfo])
|
type Option[ID comparable, AreaInfo any] func(arrangement *Arrangement[ID, AreaInfo])
|
||||||
|
|
||||||
|
type (
|
||||||
|
ConstraintHandle[ID comparable, AreaInfo any] func(editor *Editor[ID, AreaInfo], area *Area[ID, AreaInfo], item Item[ID], err error) error
|
||||||
|
ConflictHandle[ID comparable, AreaInfo any] func(editor *Editor[ID, AreaInfo], area *Area[ID, AreaInfo], item Item[ID], conflictItems map[ID]Item[ID]) map[ID]Item[ID]
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithRetryThreshold 设置编排时的重试阈值
|
||||||
|
// - 当每一轮编排结束任有成员未被编排时,将会进行下一轮编排,直到编排次数达到该阈值
|
||||||
|
// - 默认的阈值为 10 次
|
||||||
|
func WithRetryThreshold[ID comparable, AreaInfo any](threshold int) Option[ID, AreaInfo] {
|
||||||
|
return func(arrangement *Arrangement[ID, AreaInfo]) {
|
||||||
|
if threshold <= 0 {
|
||||||
|
threshold = 10
|
||||||
|
}
|
||||||
|
arrangement.threshold = threshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConstraintHandle 设置编排时触发约束时的处理函数
|
||||||
|
// - 当约束条件触发时,将会调用该函数。如果无法在该函数中处理约束,应该继续返回 err,尝试进行下一层的约束处理
|
||||||
|
// - 当该函数的返回值为 nil 时,表示约束已经被处理,将会命中当前的编排区域
|
||||||
|
// - 当所有的约束处理函数都无法处理约束时,将会进入下一个编排区域的尝试,如果均无法完成,将会将该成员加入到编排队列的末端,等待下一次编排
|
||||||
|
//
|
||||||
|
// 有意思的是,硬性约束应该永远是无解的,而当需要进行一些打破规则的操作时,则可以透过该函数传入的 editor 进行操作
|
||||||
|
func WithConstraintHandle[ID comparable, AreaInfo any](handle ConstraintHandle[ID, AreaInfo]) Option[ID, AreaInfo] {
|
||||||
|
return func(arrangement *Arrangement[ID, AreaInfo]) {
|
||||||
|
arrangement.constraintHandles = append(arrangement.constraintHandles, handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConflictHandle 设置编排时触发冲突时的处理函数
|
||||||
|
// - 当冲突条件触发时,将会调用该函数。如果无法在该函数中处理冲突,应该继续返回这一批成员,尝试进行下一层的冲突处理
|
||||||
|
// - 当该函数的返回值长度为 0 时,表示冲突已经被处理,将会命中当前的编排区域
|
||||||
|
// - 当所有的冲突处理函数都无法处理冲突时,将会进入下一个编排区域的尝试,如果均无法完成,将会将该成员加入到编排队列的末端,等待下一次编排
|
||||||
|
func WithConflictHandle[ID comparable, AreaInfo any](handle ConflictHandle[ID, AreaInfo]) Option[ID, AreaInfo] {
|
||||||
|
return func(arrangement *Arrangement[ID, AreaInfo]) {
|
||||||
|
arrangement.conflictHandles = append(arrangement.conflictHandles, handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue