{$mode objfpc} {$longstrings+} {$modeswitch duplicatelocals}
uses
	SysUtils, Math, StrUtils;

type
	float = single;

	// для полуавтоматического подсчёта ссылок: сразу после создания объекта его
	// нужно присвоить какой-нибудь Reference, и пока объект нужен,
	// он должен быть присвоен хотя бы одной Reference.
	// Таким образом, сильная ссылка на объект T хранится как v: T + life: Reference.
	Reference = IInterface;

	Vec3 = object
		x, y, z: float;
		class function Make(const x, y, z: float): Vec3; static;
		procedure &Set(const x, y, z: float);
		class function Zero: Vec3; static;
		function Length: float;
		function Normalized: Vec3;
		function ToString: string;
	end;

	operator +(const a, b: Vec3): Vec3; begin result.&Set(a.x + b.x, a.y + b.y, a.z + b.z); end;
	operator -(const a, b: Vec3): Vec3;begin result.&Set(a.x - b.x, a.y - b.y, a.z - b.z); end;
	operator *(const a: Vec3; const b: float): Vec3; begin result.&Set(a.x * b, a.y * b, a.z * b); end;

	class function Vec3.Make(const x, y, z: float): Vec3; begin result.&Set(x, y, z); end;
	procedure Vec3.&Set(const x, y, z: float); begin self.x := x; self.y := y; self.z := z; end;
	class function Vec3.Zero: Vec3; const ZeroVec3: Vec3 = (x: 0; y: 0; z: 0); begin result := ZeroVec3; end;
	function Vec3.Length: float; begin result := sqrt(sqr(x) + sqr(y) + sqr(z)); end;
	function Vec3.Normalized: Vec3; begin result := self * (1 / Length); end;
	function Vec3.ToString: string; begin result := FloatToStr(x) + ', ' + FloatToStr(y) + ', ' + FloatToStr(z); end;

	// Для динамического роста массивов:
	// if count > length(storage) then SetLength(storage, ArrayGrowStgy(count, length(storage)));
	function ArrayGrowStgy(desired, physical: SizeInt): SizeInt;
	var
		delta: SizeInt;
	begin
		Assert(desired > physical);
		if physical <= 8 then delta := 4
		else if physical <= 64 then delta := 16
		else delta := physical div 4;
		result := max(desired, physical + delta);
	end;

type
	// Менеджер текущих и новых значений.
	// BindVec3 создаёт новый вектор и возвращает Vec3Binding с автоподсчётом в life.
	// Vec3Binding.value предоставляет доступ к этому вектору, при этом
	// запись откладывается до CommitFrame, или, другими словами,
	// чтение возвращает значение с последнего CommitFrame.
	CurNewManager = class(TInterfacedObject)
	type
		Vec3Binding = class(TInterfacedObject)
			destructor Destroy; override;
		private
			mgr: CurNewManager;
			mgrLife: Reference;
			id: SizeInt;
			curValue, newValue: Vec3;
		public
			property value: Vec3 read curValue write newValue;
		end;

		function BindVec3(var life: Reference): Vec3Binding;
		procedure CommitFrame;

	private
		nVec3s: SizeInt;
		vec3s: array of Vec3Binding;
	end;

	destructor CurNewManager.Vec3Binding.Destroy;
	begin
		// Удалить вектор из менеджера.
		if Assigned(mgr) then
		begin
			mgr.vec3s[id] := mgr.vec3s[mgr.nVec3s - 1];
			mgr.vec3s[id].id := id;
			dec(mgr.nVec3s);
			mgr := nil;
		end;
	end;

	function CurNewManager.BindVec3(var life: Reference): Vec3Binding;
	begin
		if nVec3s + 1 > length(vec3s) then
			SetLength(vec3s, ArrayGrowStgy(nVec3s + 1, length(vec3s)));
		result := Vec3Binding.Create; life := result;
		nVec3s += 1;
		vec3s[nVec3s - 1] := result;
		result.mgr := self; result.mgrLife := self;
		result.id := nVec3s - 1;
	end;

	procedure CurNewManager.CommitFrame;
	var
		i: SizeInt;
	begin
		for i := 0 to nVec3s - 1 do
			vec3s[i].curValue := vec3s[i].newValue;
	end;

type
	Thing = class(TInterfacedObject)
		name: string;
		posBind: CurNewManager.Vec3Binding;
		posBindLife: Reference;

		constructor Create(const name: string; mgr: CurNewManager);
		function ToString: string; override;
		function GetPos: Vec3;
		procedure SetPos(const value: Vec3);
		property pos: Vec3 read GetPos write SetPos;
	end;

	constructor Thing.Create(const name: string; mgr: CurNewManager);
	begin
		inherited Create;
		self.name := name;
		posBind := mgr.BindVec3(posBindLife);
	end;

	function Thing.ToString: string;
	begin
		result := name + ' (' + pos.ToString + ')';
	end;

	function Thing.GetPos: Vec3;
	begin
		result := posBind.value;
	end;

	procedure Thing.SetPos(const value: Vec3);
	begin
		posBind.value := value;
	end;

var
	mgr: CurNewManager;
	mgrLife: Reference;
	things: array of record
		t: Thing;
		tLife: Reference;
	end;

	procedure CohereThingToCenter(t: Thing; const d: float);
	var
		i: SizeInt;
		center: Vec3;
	begin
		center := Vec3.Zero;
		for i := 0 to High(things) do
			center += things[i].t.pos;
		center *= 1 / length(things);
		writeln('Обновление ' + t.name + ': центр = ' + center.ToString);
		t.pos := t.pos + (center - t.pos).Normalized * d;
	end;

	function DescribeThings: string;
	var
		i: SizeInt;
	begin
		result := '';
		for i := 0 to High(things) do
			result += IfThen(i > 0, ', ') + things[i].t.ToString;
	end;

var
	i: SizeInt;

begin
	mgr := CurNewManager.Create; mgrLife := mgr;

	SetLength(things, 3);
	for i := 0 to High(things) do
	begin
		things[i].t := Thing.Create(chr(ord('A') + i mod (ord('Z') - ord('A') + 1)), mgr);
		things[i].tLife := things[i].t;
	end;

	things[0].t.pos := Vec3.Make(10, 0, 0);
	things[1].t.pos := Vec3.Make(18, 0, 0);
	things[2].t.pos := Vec3.Make(20, 0, 0);
	mgr.CommitFrame;
	writeln('Начальные позиции: ' + DescribeThings);

	for i := 0 to High(things) do CohereThingToCenter(things[i].t, 0.5);
	writeln('Позиции перед коммитом: ' + DescribeThings);
	mgr.CommitFrame;
	writeln('Позиции после коммита, придвинутые к центру на 0.5:' + LineEnding + DescribeThings);
end.